코딩 작업 (Coding Work)
설계가 완성되면 코딩 작업이 시작된다. 모듈에 대한 원시 코드를 작성하고 문서화하는 단계이다. 코딩 작업은 프로그래밍을 의미하며, 단순히 설계한 기능을 구현하는 일을 넘어 가독성을 높이고 유지보수를 편하게 하는 관점으로도 작업해야 한다. 즉, 코딩 단계에서는 품질 향상을 위한 다각도의 노력이 필요하다.
작업 과정
분석 단계, 설계 단계에서 완성된 작업물들을 가지고 코딩 단계에서는 다음의 순서를 통해 프로그래밍을 진행한다.
- 원시코드를 같은 스타일로 만들기 위해 코딩 표준을 만든다.
- 아키텍처 설계 결과 프레임워크 패키지와 응용 패키지를 결정한다. (프레임워크 패키지는 응용 패키지가 완성되기 전에 구현되어야 함)
- 패키지 내의 각 클래스에 대해 요구 사항과 상세 결과를 반영하여 메서드를 코딩한다.
- 클래스 구현이 끝나면 곧바로 설계나 코드의 결함을 찾는 인스펙션 과정을 진행한다.
- 클래스 단위로 테스트하고, 클래스나 패키지를 릴리스하여 응용 시스템으로 통합한다.
자주 발생하는 오류
코딩 과정에서는 버그를 찾고 해결하는데 많은 시간을 할애하게 된다. 소프트웨어 개발 시, 잘못된 데이터는 예상치 못한 문제를 일으킬 수 있다. 이러한 에러 처리를 위한 작업들은 매우 중요하다.
- `메모리 누수(Memory Leak)`: 동적으로 할당된 메모리를 해제하지 않아 사용되지 않는 메모리가 계속 남아 있는 현상으로 장기 실행 프로그램에서 심각한 성능 저하를 일으킨다.
- `중복된 프리 선언(Double Free Error)`: 이미 해제된 메모리를 다시 해제하려고 할 때 발생하는 오류로 프로그램 충돌이나 예측 불가능한 동작을 유발할 수 있다.
- `NULL의 사용(Null Pointer Dereference)`: NULL 포인터를 참조하거나 접근하려고 할 때 발생하는 오류이다.
- `별칭 남용 (Alias Misuse)`: 포인터나 참조가 동일한 메모리를 가리킬 때, 한 곳에서 변경한 값이 다른 곳에 예상치 못하게 영향을 줄 수 있다.
- `배열 인덱스 및 수식 예외 오류`: 배열 범위를 초과하거나 수식 계산 중 예외(예: 0으로 나누기)가 발생하는 오류. 메모리 접근 위반이나 논리 오류의 원인이 된다..
- `사용자 정의 자료형 오류(Custom Type Error)`: 구조체(struct), 클래스(class) 등을 잘못 설계하거나 사용할 때 생기는 오류이다. 초기화 누락, 잘못된 접근 등 다양한 원인이 있다.
- `스트링 처리 오류(String Handling Error)`: 문자열을 복사, 결합, 파싱할 때 경계값을 넘거나 종료 문자를 누락하는 등의 실수로 발생한다. 보안 취약점과도 연결된다.
- `버퍼 오버플로우 오류(Buffer Overflow)`: 제한된 버퍼 공간보다 많은 데이터를 입력하거나 저장하려 할 때 발생하며 프로그램 충돌이나 악성 코드 실행의 원인이 될 수 있다.
- `동기화 오류(Synchronization Error)`: 멀티스레드 환경에서 공유 자원 접근이 적절히 조율되지 않아 발생하는 오류이다. 경쟁 조건(Race Condition), 데드락(Deadlock) 등이 포함된다.
코딩 표준 (Coding Standard)
프로그래머들에게는 각자 코딩에 있어서 일관된 유형을 가지는데, 이를 코딩 스타일이라고 한다. 좋은 코딩 스타일은 딱 잡아서 정의하기는 어렵지만 좋은 스타일인지 아닌지 판별하는 기준은 “코드가 간결하고 읽기 쉬운가?”이다. 즉, 코드가 명확하고 복잡하지 않아 이해하기 쉽다는 것이다. 다음의 원칙들을 지킴으로 좋은 가독성을 지닌 코드를 작성할 수 있다.
명명 규칙 (Naming Convention)
프로그램 안에 여러 요소들에 이름을 붙이는 것은 매우 중요한데, 일정한 규칙에 따라 명확한 의미를 가지는 이름을 작성하면 협업이 쉬워지고 가독성이 높은 코드를 작성할 수 있다. 주요 명명 규칙 스타일은 다음과 같다.
- 카멜 표기법(camelCase): 첫 단어는 소문자, 이후 단어는 대문자로 시작한다. Ex: `userName`, `getUserInfo()` JavaScript, Java, TypeScript에서 변수나 함수 이름에 주로 사용한다
- 파스칼 표기법(PascalCase): 모든 단어의 첫 글자를 대문자로 설정한다. Ex: `UserInfo`, `MainComponent` 클래스 이름, 생성자 함수 등에서 자주 사용한다
- 스네이크 표기법(snake_case): 모든 글자를 소문자로 쓰고, 단어는 밑줄(_)로 구분한다. Ex: `user_name`, `total_count` Python, 일부 C 프로젝트에서 변수나 함수에 주로 사용한다.
- 케밥 표기법(kebab-case): 단어를 하이폰(-)으로 연결한다. Ex: `main-header`, `user-profile` 주로 HTML 클래스나 CSS 선택자 이름에 사용한다.
프로그래밍 내의 여러 요소에 대해 이름을 붙이는 방법은 다음과 같다. 다만, 이는 정답이 아니며 단지 예시일 뿐이다.
- 클래스와 인터페이스 이름: 일반적으로 명사 또는 명사구이며, 파스칼 표기법을 따른다.
- 메서드 이름: 일반적으로 카멜 표기법을 따르고, 프로시저는 실행하라는 명령문이기에 동사구로 표현하고 (Ex: `public void setTitle(String t) {...}`), 함수는 값을 생성하기에 명사구로 표현한다(Ex: `public double areaOfRectangle(int x, int y) {...}`),
- 변수 이름: 일반적으로 소문자로 표시한다. 변수 이름은 변수의 용도에 대한 힌트를 잘 제공해야 한다. 구체적인 이름을 사용하는 것이 좋다.
- 상수 이름: 상수는 모두 대문자로 표시하고, 단어는 밑줄로 구분한다. (Ex: `public static final double SPEED_OF_LIGHT = 299792458`)
- 패키지 이름: 일반적으로 모두 소문자이며, 명사로 정한다.
일관된 형식
프로그램 내에서 일관된 형식에 대한 규칙을 정의하고, 그 규칙대로 코드를 작성해야 한다. 대표적인 일관된 형식으로는 “들여쓰기”와 “괄호 사용”이 있다. 들여쓰기와 괄호 사용은 프로그램 구조를 명확하게 하기 위하여 사용한다. VSCode나 IntelliJ 등 많은 IDE에서는 들여쓰기와 괄호 사용을 지원하여 일관되게 형식을 둘 수 있다.
프로그램에서 되풀이되느 문장이나 수식은 메서드나 클래스로 패키지화하는 것이 좋다. 제어흐름을 나타내는 구조에서는 수식을 사용하는 것보다 블록 문장을 사용하는 것이 좋다. 이 또한 괄호 사용에 해당하는 것인데, 제어구조가 중첩되는 경우 블록 문장을 사용하면 혼란을 줄일 수 있다. 수식 내에서도 괄호(소괄호)를 사용하면 오퍼레이션의 순서를 명확히 할 수 있다.
주석 (Comment)
프로그램을 작성하고 디버깅하는 중간마다 주석을 다는 것은 중요하다. 다른 사람들이 프로그램을 이해하기 쉽도록 프로그램을 잘 문서화해야 한다. 프로그램이 잘 문서화가 되면 유지보수가 쉽다. 주석의 종류는 다음과 같다.
한 줄 주석 (Single-Line Comment)
특정 코드 라인에 대한 간단한 설명에 사용한다.
// 사용자의 입력값을 검증함
const isValid = validateInput(input);
블록 주석 (Block Comment)
여러 줄에 걸쳐 설명할 때 사용한다.
/*
이 함수는 사용자 데이터를 가져와서
프로필 화면에 표시하는 역할을 합니다.
서버 응답이 실패할 경우 예외를 발생시킵니다.
*/
function fetchUserData() {
...
}
문서 주석 (Documentation Comment)
함수, 클래스, 메서드 등의 사용법을 문서화하는 데 사용한다. JavaScript/TypeScript에서는 JSDoc, Java에서는 JavaDoc, Python에서는 docstring을 사용한다.
/**
* 두 수를 더하는 함수
* @param a 첫 번째 숫자
* @param b 두 번째 숫자
* @returns 두 수의 합
*/
function add(a: number, b: number): number {
return a + b;
}
좋은 주석의 기준은 다음과 같다.
기준 | 설명 |
What 보다는 Why | 코드는 무엇을 하는지보다 왜 그렇게 했는지 설명하는 게 더 중요하다 |
구체적이고 간결해야 함 | 너무 장황하거나 추상적인 설명은 오히려 해롭다 |
코드 변경 시 주석도 반드시 수정 | 주석이 실제 코드와 달라지면 혼란을 초래한다 |
중복 설명 금지 | 코드로 명확한 내용은 주석으로 반복하지 않는다 |
리팩토링 (Refactoring)
리팩토링은 이미 존재하는 코드의 디자인을 안전하게 향상시키는 기술로, 문제가 될 수 있는 코드를 여러 측면에서 향상시킨다. 이때, 코딩 스타일 뿐만 아니라 성능과 구조를 개선한다. 좋은 설계가 되도록 코드를 개선시키는 것이 리팩토링이다.
결국 리팩토링의 목적은 다음과 같다.
- 구조가 망가지지 않도록 디자인을 유지하면서 동시에 소프트웨어의 디자인을 개선시킨다.
- 소프트웨어를 이해하기 쉽도록 만든다.
- 버그를 더 잘 찾을 수 있도록 한다.
- 프로그램 작성을 더 빠르게 할 수 있도록 한다. 즉, 개발 속도가 빨라진다.
리팩토링 과정
리팩토링 과정은 다음의 순서대로 이루어진다.
- 소규모의 변경 - 단일 리팩토링
- 코드가 모두 잘 작동되는지 테스트
- 전체가 잘 작동하면 다음 리팩토링 단계로 넘어간다.
- 코드가 잘 작동하지 않으면 문제를 먼저 해결하고 리팩토링한 것을 undo하여 시스템이 작동되도록 유지한다.
이 과정을 반복하여 코드를 개선하며 문제를 해결한다.
코드 스멜 (Code Smell)
리팩토링은 프로그램에 대한 작업을 어렵게 만드는 것을 찾아 고치고 다듬어 나가는 것이다. 이때, 리팩토링 대상을 “코드 스멜”이라고 한다. 말 그대로 나쁜 코드의 냄새라는 것이다. 코드 스멜이 될 수 있는 요소들과 그에 대한 리팩토링 방법은 다음과 같다.
코드 스멜 | 설명 | 리팩토링 기법 및 설명 |
Long Method (긴 메서드) |
하나의 메서드가 너무 많은 일을 하며 코드 길이가 지나치게 길다 | Extract Method 메서드를 여러 개의 작은 메서드로 나눠서 가독성과 재사용성을 높인다 |
Large Class (거대한 클래스) |
클래스에 너무 많은 책임이 집중되어 복잡도가 높아진다 | Extract Class 클래스의 책임을 나누어 새로운 클래스로 분리한다 |
Feature Envy (기능 질투) |
어떤 메서드가 자기 클래스보다 다른 클래스의 데이터를 더 많이 사용한다 | Move Method 해당 메서드를 관심 있는 데이터를 가진 클래스로 옮긴다 |
Data Clumps (데이터 덩어리) |
항상 같이 다니는 변수 묶음이 반복적으로 나타난다 | Introduce Parameter Object 관련 변수들을 하나의 객체로 묶는다 |
Primitive Obsession (기본형 집착) |
문자열, 숫자 등 원시 타입을 과도하게 사용한다 | Replace Primitive with Object 의미 있는 객체로 대체해 표현력과 안정성을 높인다 |
Switch Statements (조건문 남용) |
여러 곳에 중복된 if-else나 switch 문이 존재한다 | Replace Conditional with Polymorphism 조건문 대신 다형성(상속, 전략 패턴 등)을 활용한다 |
Speculative Generality (쓸데없는 일반화) |
현재는 사용하지 않지만 미래를 대비해 만든 코드 구조가 존재한다 | Collapse Hierarchy 불필요한 추상 클래스나 인터페이스를 제거한다 |
Temporary Field (임시 필드) |
특정 조건에서만 사용되는 필드가 클래스에 존재한다 | Extract Class 조건부 데이터를 별도의 클래스로 분리한다 |
Message Chains (메시지 체인) |
a.b.c.d처럼 체인 형식으로 객체에 계속 접근한다 | Hide Delegate 중간 객체를 숨기기 위해 위임 메서드를 만들어 캡슐화한다 |
Middle Man (중개자 남용) |
클래스가 대부분 다른 객체에 대한 호출만 전달한다 | Remove Middle Man 직접 접근하도록 하여 중복 위임을 제거한다 |
Inappropriate Intimacy (부적절한 친밀감) |
클래스 간 내부 구현에 과도하게 의존한다 | Move Method/Field 책임을 적절한 위치로 옮겨 결합도를 낮춘다 |
Duplicate Code (중복 코드) |
비슷하거나 동일한 코드가 여러 곳에 반복된다 | Extract Method 중복된 코드를 하나의 메서드로 통합한다 |
Shotgun Surgery (산탄총 수술) |
하나의 변경이 여러 클래스나 파일을 동시에 수정하게 만든다 | Move Method/Field 관련 기능을 한 클래스에 모아서 변경 범위를 줄인다 |
Divergent Change (산발적 변경) |
하나의 클래스가 여러 이유로 자주 변경된다 | Extract Class 변경 이유에 따라 클래스를 분리하여 단일 책임을 유지한다 |
Lazy Class (게으른 클래스) |
너무 적은 기능만 가진 클래스가 존재한다 | Inline Class 해당 클래스를 다른 클래스에 통합한다 |
Comments (불필요한 주석) |
코드 자체로 설명될 수 있는 내용을 주석으로 설명하고 있다 | Rename/Extract 주석이 필요 없도록 변수명이나 메서드를 명확하게 바꾼다 |
코드 품질 향상 기법
코드를 작성한 이후, 다른 곳에서 사용되기 전에 오류가 없는지 검사하고 테스트해야 한다. 즉, 코드의 품질을 향상시키는 좋은 방법은 바로 “테스트”이다. 그러나 이외에도 여러 가지 코드 품질 향상 기법들이 있는데 다음과 같다.
인스펙션 (Inspection)
인스펙션은 설계든 코드든 결함을 찾아내고 이를 확인하는 작업이다. 즉, 사전에 정해진 절차에 따라 팀원들이 함께 소프트웨어 산출물을 검토하는 품질 관리 기법이다. 이를 통해 버그나 설계상의 오류, 코딩 스타일 위반, 요구사항 미반영 등의 문제를 개발 초기에 발견할 수 있다.
인스펙션의 특징은 다음과 같다.
- `목적`: 오류 발견, 품질 향상, 문서 일관성 확보
- `검토 대상`: 소스코드, 설계서, 요구사항 명세서, 테스트 케이스 등
- `검토 방식`: 개발자가 아닌 검토자들이 직접 읽고 분석한다.
- `도구 사용 여부`: 보통 사람 중심이지만, 정적 분석 도구와 병행할 수도 있다.
- `공식성`: 비공식 코드 리뷰와 달리, 정해진 절차와 역할이 있음
정적 분석 (Static Analysis)
정적 분석은 프로그램 텍스트를 조직적으로 분석하여 결함을 찾아내는 과정이다. 프로그램을 실제로 실행하지 않고, 소스 코드나 중간 코드 등을 분석하며, 컴파일 전에 코드의 구조, 문법, 스타일, 보안 취약점 등을 자동으로 점검할 수 있다.
정적 분석은 소프트웨어 도구를 사용하여 자동으로 할 수 있다. 대표적인 정적 분석 도구는 다음과 같다.
정적 분석 도구 | 언어 | 특징 |
SonarQube | Java, JS, Python 등 다수 | 코드 품질과 보안 점검에 특화된 통합 분석 플랫폼 |
ESLint | JavaScript | 문법 오류, 스타일 위반 탐지 |
Pylint / Flake8 | Python | PEP8 스타일, 코드 오류 점검 |
Cppcheck | C/C++ | 런타임 오류 없이 정적 분석 가능 |
FindBugs / SpotBugs | Java | 바이트코드 수준에서 버그 탐지 |
Infer (by Meta) | Java, C, C++ 등 | Null pointer, memory leak 자동 분석 |
테스트 중심 개발 (TDD: Test-Driven Development)
일반적으로는 프로그램을 작성하고 테스팅을 하지만 테스트 중심 개발은 테스트를 위한 코드를 먼저 작성하고 기능을 구현하도록 요구한다. 테스트는 주로 클래스 내의 메서드를 시험하는 “단위 테스팅”을 위한 것이다. TDD 진행 과정은 다음과 같다.
- TDD를 준비한다: 개발하려는 프로그램의 뼈대를 만들고 테스트 커버리지 수준을 결정한다.
- 테스트 코드를 작성한다: 구현할 기능을 정하고 동시에 기능을 테스트할 방법을 설계하고 코딩한다.
- 반복하여 기능을 구현하고 테스트한다: 미리 작성한 테스트 코드에 의해 통과될 수 있도록 기능을 조금씩 정확하게 구현한다. 테스트를 통과할 때까지 코드를 발전시키고 테스트를 수행한다.
- 테스트 커버리지 측정: 테스트가 수행되어 프로그램의 실행이 확인된 비율을 측정하여 원하는 수준에 미치지 못하면 테스트 케이스를 추가한다. 추가한 테스트 코드를 사용하여 프로그램을 테스팅하여 원하는 수준의 커버리지에 도달하면 종료된다.
TDD의 구성 요소는 다음과 같다.
- `단위 테스트(Unit Test)`: 가장 작은 단위(함수, 메서드 등)의 동작 검증
- `테스트 프레임워크`: Jest, JUnit, PyTest 등 언어에 맞는 테스트 도구 사용
- `Mocking / Stubbing`: 외부 API나 DB와 같은 의존 요소를 대체하여 테스트 수행
TDD의 장단점은 다음과 같다.
- (장점) 요구사항 기반 개발 가능 (정확한 기능 정의)
- (장점) 코드의 결함 감소 및 품질 향상
- (장점) 문서 역할을 하는 테스트 코드 자동 생성 및 자동화된 테스트 커버리지 확보
- (단점) 초기 학습 곡선이 높고 개발 속도가 느리다
- (단점) 잘못된 테스트 작성 시 잘못된 설계 유도 가능