추상화

가독성 좋은 코드를 작성하는 첫번째 원리 추상과 구체를 이해하자

💡

추상

  • 중요한 정보는 가려내어 남기고, 덜 중요한 정보는 생략하여 버린다.

파블로 피카소 - “추상은 항상 구체적인 실재에서 시작해야 한다.”

추상화는 최소 정보의 단위를 묶어서 어떻게 부를 것이냐에 관점이며, 컴퓨터 과학은 이러한 추상화 과정이 조화롭게 겹겹이 쌓여서 만들어진다

💡

💬 읽기 좋은 코드를 작성 하기 위해 추상화의 개념을 왜 알아야 할까?

  • 👉 적절한 추상화는 복잡한 데이터와 복잡한 로직을 단순화 하여 이해하기 쉽도록 돕는다
  • 👉 즉, 읽기가 좋다.

추상화 된 내용으로부터 구체적인 정보를 해석하지 못하는 경우

  1. 추상화 과정에서 중요한 정보를 부각 시키지는 못했을 때
    • 상대적으로 덜 중요한 정보를 남기고 중요한 정보는 제거함
  2. 해석자가 동일하게 공유하는 문맥이 없을 때
    • 중요한 정보의 기준이 다를 수 있음
    • 도메인 영역 별 추상화 기준이 다를 수 있다는 걸 인지 해야함
🚨
  • 잘못된 추상화가 야기하는 사이드 이펙트는 생각보다 정말로 크다.
  • 적절한 추상화는 해당 도메인의 문맥 안에서 정말 중요한 핵심 개념만 남겨서 표현 하는 것

:pencil: 추상화의 가장 대표적인 행위


🗒️ 이름을 짓는다

👉 가장 단순하면서도 아주 중요한, 고도의 추상적 사고 행위

단수와 복수를 구분하기

이름 줄이지 않기

은어/방어 사용하지 않기

좋은 코드를 보고 습득하기

Example use case:

	int row = Character.getNumericValue(r) - 1;
	if (input2.equals("2")) {
		board[row][col] = "⚑";
		boolean open = true;
		for (int i = 0; i < 8; i++) {
			for (int j = 0; j < 10; j++) {
				if (board[i][j].equals("□")) {
					open = false;
				}
			}
		}
	}
	int selectedRowIndex = Character.getNumericValue(cellInputRow) - 1;
	if (userActionInput.equals("2")) {
		board[selectedRowIndex][selectedColIndex] = "⚑";
		boolean isAllOpend = true;
		for (int row = 0; row < 8; row++) {
			for (int col = 0; col < 10; col++) {
				if (board[row][col].equals("□")) {
					isAllOpend = false;
				}
			}
		}
	}

🙋‍♂️ 한 메서드의 주제는 반드시 하나다

💡
  • 잘 쓰여진 코드라면, 한 메서드의 주제는 반드시 하나다.
  • 만약, 생략할 정보와 의미를 부여하고 드러낼 정보를 구분하기 어렵다면 메서가 2가지 이상의 일을 하고 있을 가능성이 높다.

🧰 메서드 선언(추출)

💡

반환타입 메서드명 (파라미터) {}

  • 메서드명
    • 추상화된 구체를 유추할 수 있는 적절한 의미가 담긴 이름
    • 파라미터와 연결지어 더 풍부한 의미를 전달할 수도 있다.
  • 파라미터
    • 파라미터의 타입, 개수, 순서를 통해 의미를 전달
    • 파라미터는 외부 세계와 소통하는 창구
      • 파라미터는 해당 메서드가 어떠한 행위를 하기 위해서 필요한 정보를 알려주는 것
  • 반환타입
    • 메서드 시그니처에 납득이 가는 적절한 타입의 반환 값 돌려주기
      • 반환 타입이 boolean인데 이게 이 메서드에서 무엇을 의미할까에 대한 고민이 길어진다면 다른 타입을 고민 해봐야함
    • void 대신 충분히 반환할 만한 값이 있는지 고민해보기
      • 반환값이 있다면 테스트가 용이함

Example use case:

boolean isAllOpend = true;
for (int row = 0; row < 8; row++) {
	for (int col = 0; col < 10; col++) {
		if (board[row][col].equals("□")) {
			isAllOpend = false;
		}
	}
}

if (isAllOpend) {
	gameStatus = 1;
}
private static void checkIfGameIsOver() {
    boolean isAllOpened = isAllCellOpened();
    if (isAllOpened) {
        gameStatus = 1;
    }
}

private static boolean isAllCellOpened() {
    boolean isAllOpened = true;
    for (int row = 0; row < 8; row++) {
        for (int col = 0; col < 10; col++) {
            if (board[row][col].equals("□")) {
                isAllOpened = false;
            }
        }
    }
    return isAllOpened;
}

💫 같은 패키지 내에서 추상화 레벨 맞추기

💡

책을 사러 서점에 갔다. 서점에서 책을 고르기 위해 책의 목차를 읽고 마음에 드는 책을 구매 하는데, 어느 날 보니 책들 사이에 목차가 없는 표지만 있는 책을 발견했다.

이 책을 본 고객은 당황할 수 밖에 없다. 서점의 오류인듯 아닌듯 의도된듯 아닌듯 하지만 이런 시점은 일상이 아닌 코드를 작성하는 시점에서도 발생 할 수 있다는 것이다.

코드를 읽다보니 추출된 메서드가 등장하고 메서드는 각자의 역할이 담겨져있다. 어느덧 중반부 쯤 와서 메서드 사이에 뜬금없이 자리 잡은 조건문은 구현체와 가까운 코드가 작성 되어 있는데 이런 코드는 읽는 사람이 추상화 된 메서드를 읽는 도중 만난다면 혼란을 줄 수 있다.

🎓 코드를 읽는 모듈은 추상화 레벨이 동등해야한다.

Example use case:

public static void main(String[] args) {
	showGameStartComments();
	initializeGame();
	showBoard();
	...

	if (gameStatus == 1) {  // <- 추상화 레벨이 다른 구문
		System.out.println("게임 클리어");
		break;
	}
}
public static void main(String[] args) {
	showGameStartComments();
	initializeGame();
	showBoard();
	...

	if (doesUserWinTheGame()) {
		System.out.println("게임 클리어");
		break;
	}
}

🌇 매직 넘버, 매직 스트링을 상수로 추출하기

💡
  • 매직 넘버, 매직 스트링
    • 의미를 갖고 있으나 상수로 추출되지 않은 숫자나 문자
    • 상수 추출로 이름을 짓고 의미를 부여함으로 가독성 및 유지보수 향상 가능

Example use case:

private static String[][] board = new String[8][10];
private static Integer[][] landMineCounts = new Integer[8][10];
private static boolean[][] landMines = new boolean[8][10];
private static int gameStatus = 0;
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];  
private static final Integer[][] NEARBY_LAND_MINE_COUNTS = new Integer[BOARD_ROW_SIZE][BOARD_COL_SIZE];  
private static final boolean[][] LAND_MINES = new boolean[BOARD_ROW_SIZE][BOARD_COL_SIZE];  
public static final int LAND_MINE_COUNT = 10;  
public static final String FLAG_SIGN = "⚑";  
public static final String LAND_MINE_SIGN = "☼";  
public static final String CLOSED_CELL_SIGN = "□";  
public static final String OPENED_CELL_SIGN = "■";

👉 예시처럼 자주 사용 되거나 의미를 갖고 있는 숫자나 문자를 뜻 함