개발독서/코드품질

[켄트 벡의 Tidy First?] 9~16장 - 더 나은 소프트웨어 설계를 위한 32가지 코드 정리법

보리시스템 2024. 8. 1.

[Part 1 코드 정리법]
09 설명하는 상수 
10 명시적인 매개변수
11 비슷한 코드끼리 
12 도우미 추출 
13 하나의 더미 
14 설명하는 주석 
15 불필요한 주석 지우기 

[Part 2 관리]
16 코드 정리 구분

 


 

Part 1 코드 정리법

 

09 설명하는 상수 

- 리터럴 상수(literal constant)는 상징적인 상수(symbolic constant)로 만들기
=> 리터럴 상수? 소스 코드에 기록된 텍스트 표현한 것
=> 상징적인 상수? 변수처럼 고정 값 클래스 중 하나를 취할 수 있는 기호를 써서 상수를 정의한 것
// 코드정리 전
if response.code = 404


// 코드정리 후
PAGE_NOT_FOUND := 404
if response.code = PAGE_NOT_FOUND

 

- 같은 리터럴 상수가 두 곳에서 쓰일 때 다른 의미로 쓰이는지 확인
=> 한번에 바뀌어야 하거나 함께 이해해야 하는 상수들을 한곳에 모아두고, 다른 이유로 묶인 변수들은 분리
=> ONE = 1과 같이 빈약한 의미를 가지는 상수에 대해서는 굳이 상징적인 상수로 정리할 필요는 없음
// 나쁜 예시
ONE = 1

 

[BOLEE 질문] 1, 2번 코드 중 더 나은 코드는? 이유는?

1번
return await this.boardService.save(id);

2번
const board = await this.boardService.save(id);
return board

 



10 명시적인 매개변수

- 루틴에서 매개변수를 명시적으로 전달하지 않으면 코드를 읽을 때 어떤 데이터가 필요한지 알기 어려움
=> 이런 경우 이후에 매개변수를 변경해 암묵적으로 사용하는 일이 발생할 수 있음

*루틴? 하나의 목적에 도달하기 위한 개별적인 함수나 프로시저, 메서드
// 맵에서 매개변수가 블록으로 전달되는 경우가 흔히 발생함
// 코드정리 전
params = { a: 1, b: 2 }
foo(params)

function foo(params) // 이후에 params.a, params.b의 형태로 사용


// 코드정리 후
function foo(params)
	foo_body(params.a, params.b)
    
function foo_body(a, b) // 이후에 a, b의 형태로 사용

 

  • [BOLEE] 코드 더보기
  코드정리 전 코드정리 후
TypeScript
class UserService {
  private currentUser: User;

  constructor(user: User) {
    this.currentUser = user;
  }

  getUserInfo() {
    // currentUser가 어디서 오는지 명확하지 않음
    return this.currentUser.info;
  }
}

const user = new User('John Doe', 'john@example.com');
const userService = new UserService(user);
console.log(userService.getUserInfo());


class UserService {
  getUserInfo(user: User) {
    // user 매개변수를 명시적으로 전달받음
    return user.info;
  }
}







const user = new User('John Doe', 'john@example.com');
const userService = new UserService();
console.log(userService.getUserInfo(user));

Java
public class UserService {
  private User currentUser;

  public UserService(User user) {
    this.currentUser = user;
  }

  public String getUserInfo() {
    // currentUser가 어디서 오는지 명확하지 않음
    return this.currentUser.getInfo();
  }
}

User user = new User("John Doe", "john@example.com");
UserService userService = new UserService(user);
System.out.println(userService.getUserInfo());


public class UserService {
  public String getUserInfo(User user) {
    // user 매개변수를 명시적으로 전달받음
    return user.getInfo();
  }
}







User user = new User("John Doe", "john@example.com");
UserService userService = new UserService();
System.out.println(userService.getUserInfo(user));



 

[BOLEE 질문] 실제로 매개변수를 명시적으로 전달하지 않아 이후에 매개변수를 변경해 암묵적으로 사용한 적이 있는지?

 



11 비슷한 코드끼리

- 긴 코드 덩어리에서 구분이 되는 두 부분 사이에 빈 줄을 넣어 분리 
=> 제대로 된 SW 설계는 유연성을 확보해 이후의 설계를 더 쉽게 만들지만, 그렇지 않은 경우 큰 문제가 될 수 있음

 



12 도우미 추출 

- 메서드 추출 리팩터링
=> 루틴 속 코드 중 목적이 분명하고 나머지 코드와는 상호작용이 적은 코드 불록의 경우 도우미(helper)로 추출
=> 도우미는 작동 방식이 아닌 목적에 따라 네이밍하는 것이 좋음

 

  • [BOLEE] 코드 더보기
// 도우미 네이밍

// 작동방식에 따른 네이밍 => 함수의 내부 구현에 대한 정보
function bubbleSort ...
function processData ...
function manageUsers ...


// 목적에 따른 네이밍 => 함수가 무엇을 하는지, 어떤 목적을 달성하는지에 대한 정보
function sortArray ...
function filterInvalidEntries ...
function deactivateInactiveUsers ...

 

  • 도우미 추출 시 특수한 경우
1) 큰 루틴 안에서 몇 줄을 변경해야 하는 경우? 
해당 줄들을 도우미로 추출하고 도우미 안의 내용만 변경한 뒤 적절하다고 판단한 뒤에 도우미를 호출하는 문장에 반영
// 코드정리 전
routine()
  그대로 두는 코드
  바꾸려는 코드
  그대로 두는 코드


// 코드정리 후
helper()
  바꾸려는 코드
routine()
  그대로 두는 코드
  helper()
  그대로 두는 코드

 

2) 호출 순서를 보장하기 위한 시간적 결합을 표현하는 경우 ( b()보다 먼저 a()를 호출해야 하는 경우 )?
// 코드정리 전
foo.a()
foo.b()


// 코드정리 후
ab()
    a()
    b()

 

  • [BOLEE] 코드 더보기
  코드정리 전 코드정리 후
*ab()를 호출하면 a()와 b()가 순서대로 실행됨
TypeScript const foo = new Foo();
foo.a();
foo.b();
















class Foo {
    a(): void {
        // a()의 작업
    }

    b(): void {
        // b()의 작업
    }

    ab(): void {
        this.a();
        this.b();
    }
}

// 사용 예시
const foo = new Foo();
foo.ab(); 

Java
foo.a();

foo.b();


















public class Foo {
    public void a() {
        // a()의 작업
    }

    public void b() {
        // b()의 작업
    }

    public void ab() {
        a();
        b();
    }
}

// 사용 예시
Foo foo = new Foo();
foo.ab(); 

 



13 하나의 더미 

- 코드가 너무 여러 작은 조각으로 나뉘어져 있다면 필요한 만큼의 코드를 하나의 더미처럼 느껴질 때까지 흩어진 코드를 모아 정리
=> 코드를 만드는 데 가장 큰 비용은 코드 작성이 아닌 이해하는 데 드는 비용임
=> 작은 코드 조각을 지향하는 목적은 코드를 한 번에 조금씩 이해할 수 있도록 하는 것이기도 하지만 때로 작은 조각들을 서로 엮어 이해하기에 어렵게 되기도 함

- 관련 예시?
1) 길고 반복되는 인자(argument) 목록
2) 반복되는 코드 및 조건문
3) 부적절한 이름의 도우미
4) 공유돼 변경에 노출된 데이터 구조

 


 

14 설명하는 주석 

- 코드에서 명확하지 않은 내용에 대해 다른 사람의 관점에서 생각하고 예상 질문에 대해 주석을 추가
// 코드의 결함을 발견한 경우의 주석 예시

// 새로운 경우를 한 개 더 추가하려면 ../foo를 반드시 변경해야 합니다.
[BOLEE 질문] 주석 작성 여부를 결정하는 자신만의 기준은 어떻게 되는지?

 

  • [BOLEE] 좋은 주석, 나쁜 주석? (참조 클린코드)

https://velog.io/@hangem422/clean-code-comment

 

좋은 주석과 나쁜 주석

잘 달린 주석은 그 어떤 정보보다 유용합니다. 경솔하고 근거 없는 주석은 코드를 이해하기 어렵게 만듭니다. 오래되고 조잡한 주석은 거짓과 잘못된 정보를 퍼뜨려 해악을 미칩니다.

velog.io

 



15 불필요한 주석 지우기 

- 코드만으로 내용을 모두 이해할 수 있다면 주석 삭제하기
=> 시간이 지나 코드가 변경될 경우 주석과 코드가 서로 맞지 않거나 주석이 불필요한 경우가 생길 수 있음
=> 코드를 읽는 사람의 시간도 비용이므로 불필요한 주석은 삭제해야 함
// 코드 정리 전
if (! generator)
  // generator가 없다면 default 반환
  return getDefaultGenerator()
  
    ...generator 설정 관련 코드
    
    
// 코드 정리 후 (주석 삭제)
if (! generator)
  return getDefaultGenerator()
  
    ...generator 설정 관련 코드

 



Part 2 관리

- 코드 정리는 리팩터링으로 가는 관문
=> 코드 정리를 개인 개발 흐름에 맞추는 방법!
1) 코드 정리는 언제 시작하나요?
2) 코드 정리는 언제 멈추나요?
3) 코드의 구조를 변경하는 코드 정리와 시스템의 동작 변경을 어떻게 결합할 수 있을까요?

 


 

16 코드 정리 구분

- 코드 정리는 별도의 PR로 만들고, PR당 몇 개의 코드 정리만 넣기
 
- 코드 정리 단계
1) 변경을 구분하지 않은 상태에서 다수의 변경을 반영
=> 변경 대상? 프로그램 동작 변경(프로그램을 실행하면서 찾아내기), 프로그램 구조 변경(코드를 자세히 보며 찾아내기)
=> 공통 흐름을 알아채 비슷한 코드끼리 정리해 설명하는 도우미 만들기

2) 순서가 있는 일련의 코드 정리는 또는 동작 변경을 별도의 PR로 만들기
=> 크고 포괄적인 PR? 전체 그림을 보여주지만, 검토하는 입장에서 유용한 피드백을 제공하기에 너무 큰 덩어리일 수 있음
=> 아주 작은 PR? 소소한 피드백을 유도할 수 있지만 무시될 수도 있음
[BOLEE 질문] PR 크기에 있어 '크고 포괄적인', '아주 작은'을 판단하는 정량적인 기준이 있는지?