클린 코드 - 추상화

FreshBreeze·2024년 10월 25일
post-thumbnail

추상화

사물을 정확하게 이해하기 위해, 여러 측면 가운데 특정 측면을 가려내어 포착하는 것.
⇒ 중요한 정보만 가려내어 남기고, 덜 중요한 정보는 생략하여 버리는 행위.

적절한 추상화란?

적절한 추상화는 도메인의 문맥 안에서 핵심 개념만을 표현하는 데 집중하여 복잡한 데이터와 로직을 단순화하고, 이해와 재현을 돕는 과정을 의미합니다. 적절한 추상화에서는 정보를 통해 의미를 유추 및 재현에 성공할 수 있고, 복잡한 데이터 및 로직을 단순화하여 이해하가 쉬워집니다.
도메인의 문맥이란 각 도메인 영역에서 추상화 기준이 다를 수 있다는 뜻입니다. 예를 들어, '지하철'이라는 단어는 미국에서는 "subway", 영국에서는 "underground"라고 표현하는 것처럼, 동일한 개념도 해석자가 공유하는 도메인에 따라 다르게 표현될 수 있습니다. 따라서 추상화 시 해석자가 속한 도메인의 추상화 기준을 준수하는 것이 중요합니다.

잘못된 추상화란?

  1. 추상화 과정에서 중요한 정보를 부각시키지 못했다.
    • 덜 중요한 정보를 남기고 중요한 정보를 제거.
  2. 해석자가 동일하게 공유하는 문맥(Context)이 없다.
    • 중요한 정보의 기준이 다를 수 있다.
    • 도메인 영역 별 추상화 기준이 다를 수 있다.

이름 짓기

이름 짓기는 추상화의 대표적인 행위로, 단순하면서도 고도의 추상적 사고를 요구하는 중요한 과정입니다. 이름을 짓는다는 것은 도메인의 문맥 안에서 이해되는 용어를 바탕으로, 구체적인 대상에서 핵심 개념만을 추출해 효과적으로 표현하는 것을 의미합니다.

  1. 단수와 복수 구분
    • 끝에 -(e)s를 붙여 데이터가 (변수, 클래스 등) 단수인지 복수인지 나타내기
  2. 이름 줄이지 않기
    • 관용어처럼 자주 사용하는 줄임말 정도만 ex) column → col
  3. 은어/ 방언 사용 X
    • 도메인 용어 사용하기 ( 도메인 용어 사전 )
  4. 좋은 코드를 보고 습득하기
    • 비슷한 상황에서 자주 사용하는 단어, 개념 습득 ex) pool, candidate, threshold
// As-Is
for (int i = 0; i < 8; i++) {
    for (int j = 0; j < 10; j++) {
        board[i][j] = "□";
    }
}
String input = scanner.nextLine();
String input2 = scanner.nextLine();

// To-Be
// 별다른 의미가 없는 이름을 의도가 드러나도록 변경
for (int row = 0; row < 8; row++) {
    for (int column = 0; column < 10; column++) {
        board[row][column] = "□";
    }
}
String cellInput = scanner.nextLine();
String userActionInput = scanner.nextLine();

메서드

잘 작성된 코드에서는 한 메서드가 반드시 하나의 주제만 다루어야 합니다. 메서드 이름을 통해 구체적인 내용을 추상화하고, 필수적인 정보와 생략할 정보를 구분하는 것이 중요합니다.
추상화된 이름으로 구체적인 내용을 유추하기 어렵다면, 해당 메서드가 두 가지 이상의 작업을 수행하고 있을 가능성이 큽니다. 이 경우, 의미를 분명히 담기 위해 더 작은 단위의 메서드로 분리해야 합니다. 메서드 추출은 코드 줄 수가 아닌 의미를 기준으로 이루어지기 때문에, 한줄의 메서드도 상관없습니다.

메서드명

  • 추상화된 구체를 유추할 수 있는, 적절한 의미가 담긴 이름
  • 파라미터와 연결지어 더 풍부한 의미를 전달할 수도 있음

파라미터

  • 파라미터의 타입, 개수, 순서를 통해 의미를 전달
  • 파라미터는 외부 세계와 소통하는 창
    • 메서드 작성 사람과 사용 사람간의 필요한 재료를 알려주는 역할

반환타입

  • 메서드 시그니처에 납득이 가는, 적절한 타입의 반환값 돌려주기
    • 반환타입 boolean이 무엇을 의미하는건지?
  • void 대신 충분히 반환할 만한 값이 있는지 고민해보기
    • 반환값이 있어야 테스트도 용이해진다.
 // As-Is
 // 두 가지 이상의 일을 하는 메서드 
 private static void checkIfAllCellIsOpened() {
    boolean isAllOpened = true;
    for (int row = 0; row < 8; row++) {
        for (int col = 0; col < 10; col++) {
            if (board[row][col].equals("□")) {
                isAllOpened = false;
            }
        }
    }
    if (isAllOpened) {
        gameStatus = 1;
    }
}

// To-Be
// 한 가지 일을 집중적으로 확인하는 메서드 
private static void checkIfGameIsOver() {

	// 보드 내의 셀 들의 상태를 확인하는 메서드는 따로 분리한다. 
    boolean isAllOpened = isAllCellIsOpened();
    if (isAllOpened) {
        gameStatus = 1;
    }
}

추상화 레벨

하나의 세계 안에서는 추상화 레벨이 동등해야 합니다.

“레몬에이드”라는 단어도 추상화 레벨에 따라 다른 방식으로 표현될 수 있습니다.

  • 높은 추상화 레벨: "레몬에이드"
  • 중간 추상화 레벨: "레몬즙과 사이다를 섞어 만든 음료"
  • 낮은 추상화 레벨: "레몬을 짜서 나온 액체와 레몬향 탄산음료를 섞어 만든 음료"

이처럼 중요한 정보를 얼마나 간추렸는지에 따라 추상화 레벨이 결정됩니다.

  • 외부 세계: 추상화 레벨이 높은 상태
  • 내부 세계: 추상화 레벨이 낮은 상태

메서드를 추출할 때, 내부 세계(메서드 안)와 외부 세계(메서드 밖)로 나뉘며, 각 세계는 다른 추상화 레벨을 갖습니다.

// As-Is
// 너무 구체적인 내용을 if 안에 담지 않았는가?
public static void main(String[] args) {

    showGameStartComments();
	initializeGame();
	showBoard();
	...
        
	// gameStatus == 1 이 무엇을 뜻하는가?? 
    if (gameStatus == 1) {
        System.out.println("지뢰를 모두 찾았습니다. GAME CLEAR!");
        break;
    }
}

// To-Be
// 추상화 레벨을 비슷하게 
public static void main(String[] args) {

    showGameStartComments();
	initializeGame();
	showBoard();
		...
		
    if (doesUserWinTheGame()) {
        System.out.println("지뢰를 모두 찾았습니다. GAME CLEAR!");
        break;
    }
}

매직 넘버, 매직 스트링

상수로 추출한다는 것은 이름을 부여하는 것입니다.

  • 의미가 있으나 상수로 추출되지 않은 숫자나 문자열 등
  • 상수로 추출하여 이름을 짓고 의미를 부여하면 가독성과 유지보수성이 향상됩니다.
// As-Is
// 의미 파악이 어려운 매직 넘버
private static String[][] board = new String[8][10];

// To-Be
public static final int BOARD_ROW_SIZE = 8;
public static final int BOARD_COL_SIZE = 10;
private static final String[][] BOARD = new String[BOARD_ROW_SIZE][BOARD_COL_SIZE];

출처

Readable Code: 읽기 좋은 코드를 작성하는 사고법 (박우빈)

0개의 댓글