Chap 09. 코딩

윤희빈·2026년 7월 23일

코딩 로드맵

  • 소프트웨어 제품의 품질은 결국 원시 코드에 귀결


1. 코딩 작업

  • 설계 명세대로 요구를 만족하도록 프로그래밍
  • 오류가 적고 품질 좋은 프로그램을 만드는 것이 목표

1.1 작업 과정

  1. 같은 스타일을 위해 코딩 표준 수립
  2. 아키텍처 결과로 프레임워크 패키지/응용 패키지 결정
  3. 클래스 구현이 끝나는 대로 인스펙션
  4. 클래스 단위 테스트
  5. 클래스/패키지를 릴리스하여 응용 시스템으로 통합

1.2 자주 발생하는 오류

  • 메모리 누수
  • 중복된 free
  • NULL 사용
  • 별칭 남용
  • 배열 인덱스 오류
  • 수식 예외 (0으로 나누기 등)
  • off-by-one
  • 사용자 정의 자료형 오류 (오버/언더플로)
  • 스트링 처리 오류
  • 버퍼 오류 (오버플로)
  • 동기화 오류 (데드락/레이스컨디션)

(1) 메모리 누수(Memory leak)

  • 메모리가 free되지 않고 계속 할당되는 현상(장기 실행 시스템에 치명적).
char * foo(int s)
{
  char *output;
  if (s > 0)
    output = (char *) malloc (size);
  if (s == 1 
    return NULL; /* if s==1 then memory leaked */
  return(output);
}

(2) 중복된 free 선언(Double free)

  • 이미 해제된 자원을 또 free하면 오류.
main()
{
  char *str;
  str = ( char * ) malloc (10);
  if (global == 0)
    free(str);
  free(str); /* str is already freed */
}

(3) NULL 사용/접근 오류

  • NULL이 가리키는 곳을 접근하려 하면 오류, 시스템 다운 유발.
char *ch = NULL;
if (x > 0)
{
  ch = 'c';
}
printf("%c", *ch); // ch may be NULL

*ch = malloc(size);
ch = 'c'; // ch will be NULL if malloc returns NULL
  • NULL 접근 오류와 유사한 것으로 초기화되지 않은 메모리를 접근하는 오류가 있다.
switch(i)
{
		case 0: s = OBJECT_1; break;
		case 1: s = OBJECT_2; break;
}
return (s); //s not initialized for values other than 0 or 1

(4) 별칭의 남용

  • 별칭(alias)은 많은 문제를 야기
  • 서로 다른 주소 값을 예상하고 사용한 두 개의 변수의 값이 별칭 선언으로 인하여 같은 값이 되었을 때 오류 발생

(5) 배열 인덱스 오류(Array index error)

  • 인덱스가 한도를 벗어나거나 음수면 예외 오류.
dataArray[80];
for (i = 0; i <= 80; i++)
  dataArray[i] = 0;

(6) 수식 예외 오류

  • 0으로 나누는 오류
  • 변동 소수점 예외 오류

(7) 하나 차이에 의한 오류

  • 0으로 시작하여야 할 것을 1로 시작
  • <=N으로 써야 할 곳에 <N을 쓴 경우
  • 컴파일러나 테스트 도구에 의하여 검출되지 않는 경우가 많음

(8) 사용자 정의 자료형 오류(오버/언더플로)

  • 사용자 정의 자료형 값을 다룰 때 오버플로/언더플로가 쉽게 발생하므로 주의.
typedef enum{A, B, C, D} grade;
void foo(grade X)
{
  int l, m;
  l = GLOBAL_ARRAY[x-1]; //Underflow possible
  m = GLOBAL_ARRAY[x+1]; //Overflow possible
}

(9) 스트링 처리 오류

  • strcpy, sprint 등 많은 스트링 처리 함수가 있다.
  • 매개변수가 NULL이거나 스트링이 NULL로 끝나지 않았을 경우
  • source 매개변수의 크기가 destination매개변수보다 크지 않을 경우

(10) 버퍼 오류(Buffer overflow)

  • 입력을 버퍼에 복사할 때 과도한 입력이 들어오면 스택 버퍼 오버플로 발생
  • 버퍼 오버플로는 공격에 악용될 수 있음.
void mygets(char *str) {
  int ch;
  while ( ch = getchar() != '\n' && ch != '\0' )
    *(str++) = ch;
  *str = '\0';
}

main() {
  char s2[4];
  mygets( s2 );
}

(11) 동기화 오류(Synchronization error)

  • 병렬 프로그램에서 공통 자원 접근 시 흔히 발생
  • 대표 문제
    • 데드락(deadlock): 서로 자원을 점유하고 놓지 않음
    • 레이스 컨디션(race condition): 실행 순서에 따라 결과가 달라짐
    • 모순 동기화: 공유 변수 접근 시 락/언락 오류

2. 코딩 표준(Coding Standard)

  • 기준: 간결하고 읽기 쉬운 것
    • 간결함: 복잡하지 않고 명확
    • 가독성: 훑어보거나 이해하기 쉬움
  • 설계에서 높은 응집/낮은 결합을 달성하면 모듈이 더 간단해짐

2.1 명명 규칙

카멜 케이스(camelCase)

변수/필드/메소드 등(예: thisIsAnExample)

상수

대문자(예: SPEED_OF_LIGHT)

클래스/인터페이스

명사(구), 대문자 시작

메서드

소문자 시작 동사구(setTitle)

값을 설명하는 명사구 함수(areaOfTriangle)

getter/setter, boolean은 get, is 관례

변수

용도 힌트 제공, 모호한 이름 피함, 위치에 따라 길이 조절(매개변수 짧게/필드 길고 의미있게)

패키지 이름

소문자 명사

헝가리안 표기법(Hungarian Notation)

  • 변수명 앞에 타입/목적 힌트를 붙이는 방식(예: lAccountNum, strName)

2.2 형식(Formatting)

들여쓰기와 괄호

  • 프로그램 구조를 명확하게 하기 위해 사용
  • 문장의 일부와 선언문은 들여 쓰기

블록은 항상 괄호를 사용

  • 블록은 항상 {} 사용(의도와 다른 실행 방지)
    if (flag) validate(); update();
    if (flag) {
    		validate();
    		update();
    }
  • 키워드와 괄호 사이 공백으로 제어구조/메서드 호출 구분
    // Good
    if (username == null) {...}
    //
    Less Good if(username == null) {...}

2.3 문장과 수식

  • 긴 문장/수식은 메소드/클래스로 패키징

블록 문장

  • 제어구조가 중첩되었을 때 생기는 혼란을 줄일 수 있음
if (x >= 0)
		if (x > 0) positiveX();
else // 들여쓰기 때문에 첫번째 if에 관련된 것으로 착각함!
		negativeX()
if (x >= 0){
		if (x > 0) positiveX();
}
else { // 우리가 의도한 코드
		negativeX()
}

수식

  • 중첩 if 혼란 주의, 수식은 괄호로 우선순위 명확화
// Extraneous but useful parentheses.
int width = (( buffer * offset ) / pixelWidth ) + gap;

2.4 오류 처리

  • 잘못된 데이터를 어떻게 다룰지 정의
    • 예: 실제로는 없는 계좌번호

매개변수 오류

  • evaluate()라는 메소드가 매개변수로 ‘car’, ‘truck’, ‘bus’만을 받아들인다.
    → 잘못된 매개변수가 전달될 수 있으므로 스트링 타입을 매개변수로 사용하지 않는 것이 좋다.
evaluate(String vehicleP) //스트링으로만 한정하면 잘못된 값 들어올 수 있음

evaluate(SpecializedVehicle vehicleP) //확실한 매개변수를 가진 함수를 사용

입력 오류

  • 리스트박스
  • 디폴트 값을 지정

2.5 주석(Comment)

  • 디버깅 도움 + 타인이 이해하기 쉽게

과도한 주석

  • 불필요한 단어는 생략
  • 과도한 주석 피함

클래스 불변조건 (invariant)

  • 클래스의 속성에 대한 의미와 제약 기술
private int hr; // The hour of the day, in 0..23.
private double[] temps; // temps[0..numRecorded-1] are the recorded temperatures
private int numRecorded; // number of temperatures recorded

메서드 주석

  • 선행조건 포함, @param, @return 등
  • 메서드가 하는 일을 설명하는 Javadoc 스펙을 선행 조건과 함께 쓴다
@param b one of the sides of the triangle
@return The area of the triangle
  • 무언가를 수행한다는 문장으로 작성하여 관련된 모든 매개 변수를 언급
/** Print the sum of a and b. */
public static void printSum(int a, int b) { ... }

클래스 주석

  • 파일/클래스 시작부: 클래스 용도/작성자/수정일 등
/** An object of class Auto represents a car.
Author: Eun Man Choi.
Date of last modification: 25 November 2019 */
public class Auto { ... }

문장 주석

  • 논리적 단위로 그룹화
// Truthify x >= y by swapping x and y if needed.
if (x < y) {
		int tmp= x;
		x= y;
		y= tmp;
}

3. 설계에서 코드 생성

  • IDE 도구
    • UML 다이어그램으로부터 원시코드 골격 자동 생성 가능

3.1 연관(Association)의 코딩

  • 1:1 연관: A에서 B 메소드 호출 필요 → A가 B 참조를 가짐(반대도 동일)
  • 1:N 연관: A가 B 참조를 모음(컬렉션)으로 가짐
  • N:N 연관: 연관 클래스를 도입해 1:N 관계로 바꾸기도 함

3.2 시퀀스 다이어그램 코딩

  • A 객체에서 B 객체로 나가는 메시지가 있으면 메서드는 B 클래스에 정의
    public class CheckoutConroller {
        Patron p;
    
        public String checkout(String callNo) {
            BDNgr dbm = new DBMge();
            Document d = dbm.getDocument(callNo);
            String msg = " ";
    
            if (d != null) {
                Loan l = new Loan(p, d);
                dbm.save(l);
                d.setAvailable(false);
                dbm.save(d);
                msg = "Checkout successful.";
            } else {
                msg = "Document not found.";
            }
    
            return msg;
        }
    }

3.3 상태 다이어그램의 코딩


4. 리팩토링(Refactoring)

4.1 리팩토링 개념

  • 결과 동작을 바꾸지 않고 코드 구조를 재조정
  • 기존 코드 디자인을 안전하게 향상
  • 가독성↑ 유지보수성↑ 목적
  • 소프트웨어를 보다 쉽게 이해할 수 있고 적은 비용으로 수정할 수 있도록 겉으로 보이는 동작의 변화 없이 내부구조를 변경하는 것

리팩토링 목적

  • 디자인 개선
  • 이해 쉬움
  • 버그 찾기 도움
  • 더 빨리 작성 가능

4.2 리팩토링 과정


1. 소규모의 변경 - 단일 리팩토링
2. 코드가 전부 잘 작동되는지 테스트
3. 전체가 잘 작동하면 다음 리팩토링 단계로 전진
4. 작동하지 않으면 문제를 해결하고 리팩토링 한 것을 되돌려 시스템이 작동되도록 유지

4.3코드 스멜(Code Smell)

  • 리팩토링이 필요하다는 징후
    - 읽기 어려움
    - 중복된 로직을 가진 프로그램
    - 실행 중인 코드를 변경해야 하는 특별한 동작을 요구하는 프로그램
    - 복잡한 조건문이 포함된 프로그램

4.4 리팩토링 사례

메서드 추출

재사용할 확률이 많은 코드는 메서드로 정의하고 이를 호출한다.

...
if (i != min) {
    int temp = num[i];
    num[i] = num[min];
    num[min] = temp;
}
...
if (i != min) {
    Swap(ref num[i], ref num[min]);
}

void Swap(ref int a, ref int b) {
    int temp = a;
    a = b;
    b = temp;
}

클래스 추출

Custom 클래스의 일부로 phone이 포함되어 있는 것은 클래스 하나에 고유한 책임을 갖게 만드는 객체지향 모델의 정신과 맞지 않는다. 따라서 두 개의 단일 책임 클래스로 분할한다.

public class Customer {
    private String name;
    private String workPhoneAreaCode;
    private String workPhoneNumber;
}
public class Customer {
    private String name;
    private Phone workPhone;
}

public class Phone {
    private String areaCode;
    private String number;
}

서브 클래스 추출

메서드 이동

인터페이스 추출

템플릿 메서드 형성

5. 코드 품질 향상 기법

  • 코드 인스펙션
    • 프로그램을 읽어보고 눈으로 확인하는 방법
  • 정적 분석
    • 수행되지 않는 데드코드가 없는지, 선언이 되지 않고 사용한 변수가 없는지 등을 검사
  • 페어 프로그래밍
    • 애자일 방법에서 프로그래밍과 테스팅을 담당하는 두 사람이 머신을 공유하며 코딩

5.1 코드 인스펙션(Code Inspection)

  • 컴파일 + 정적 분석 도구 검사 후에 수행
  • 코드에 묻힌 결함 탐지(효율성, 표준 준수 등)
  • 결함 심각도
    • 결함의 우선순위를 나타냄
  • 결함 타입
    • 로직 문제
    • 컴퓨팅 문제 (잘못된 수식, 오차, 부호 오류)
    • 인터페이스/타이밍 문제 (인터럽트 처리 부정확, I/O 타이밍 부정확, 서브루틴/모듈 불일치)
    • 데이터 처리 (초기값 부정확, 데이터 접근 저장 부정확, 자료 값의 스케일 또는 단위 부정확..)

5.2 정적 분석(Static Analysis)

  • 프로그램 텍스트를 조직적으로 분석해 결함 탐지
  • 컴파일 시간이나 코드 작성 중에 이루어짐
  • 소프트웨어 도구를 이용하여 자동으로 가능

코딩 스타일 및 일관성 검사

버그 및 취약점 탐지

코드 복잡도 분석

의존성 관리 및 라이브러리 검사

  • 결함을 찾는데 두 가지 방법 사용
    • 코드에 존재하는 결함으로 나타날 비정상적인 패턴이나 원하지 않느 패턴을 찾는 방법
    • 실행할 때 프로그램의 고장을 일으킬 코드 상에 존재하는 결함을 직접 찾는 방법
  • 자료 변칙(data anomaly): 정의되지 않고 사용 등 데이터 흐름 이상 패턴
  • 사족(redundant): 중복 배정문, 데드 코드, 조건 중복 등(컴파일러가 못 잡을 수 있음)

5.3 테스트 중심 개발(TDD)

  • 테스트 코드를 작성한 후 기능 구현
  • 주로 클래스 안의 메서드를 시험

개발 과정

TDD 작업 과정

  1. TDD를 준비
  2. 테스트 코드 작성
  3. 반복하여 기능을 구현하고 테스트
  4. 테스트 커버리지 측정

테스트 코드

  • 대상
    public class MyUnit {
        public String concatenate(String one, String two) {
            return one + two;
        }
    }
  • 테스트
    import org.junit.Test;
    import static org.junit.Assert.*;
    
    public class MyUnitTest {
        
        @Test
        public void testConcatenate() {
            MyUnit myUnit = new MyUnit();
            String result = myUnit.concatenate("one", "two");
            
            assertEquals("onetwo", result);
            //예상하는 출력(onetwo)와
            //실제 호출된 메서드 result의 출력을 비교
        }
    }
    • @Test: JUnit에게 신호를 보내는 것으로 실행되어야하는 단위 테스트임을 나타냄.
    • assertEquals(): “실제 호출된 메서드의 출력”을 “예상하는 출력”과 비교한다.
      • 두 값이 같으면 정상적으로 리턴되고 실행이 계속됨.
      • 두 값이 다르면 예외가 발생하고 테스트가 중단됨

5.4 짝 프로그래밍(Pair Programming)

  • 두 사람이 같은 컴퓨터를 사용하며 함께 프로그래밍

  • 소통 향상, 상호 학습, 창의적 문제 해결에 도움

  • 드라이버: 키보드를 사용하여 실제로 코드를 작성하는 개발자로 코드의 구체적인 구현에 집중한다.

  • 내비게이터: 코드를 실시간으로 검토하고, 설계나 전략적인 방향을 제시한다. 네비게이터는 코드의 논리적 오류나 개선점을 찾아내고 드라이버에게 피드백을 제공한다.

장점

  • 높은 코드 품질
  • 지식 공유
  • 빠른 문제 해결
  • 동기 부여

단점

모든 사람에게 맞지는 않는다. 혼자 개발하는 것을 선호나는 사람들도 존재함.

적절히 다루지 못하면 대화에 시간이 너무 많이 걸린다.

파트너와의 교육, 경험, 코딩 스타일 등의 차이점에 적응해야한다.


profile
비니비니히비니의 정리블로그

0개의 댓글