3장 : 긴 코드 조각내기
의문점
- 불변속성(invariant), 가정설정문(assertion)
- 디자인패턴 → 상속보다는 컴포지션, 컴포지션이 컴파일 오류 발생 만듬?
학습목표
- 다섯줄 제한(FIVE LINES)으로 지나치게 긴 메서드 식별하기란?
- 세부사항을 보지 않고 코드 작업하기란?
- 메서드 추출(EXTRACT METHOD)로 긴 메소드 분해하기란?
- 호출 또는 전달, 한가지만할것(EITHER CALL OR PASS)으로 추상화 수준에 맞추기란?
- if 문은 함수의 시작에만 배치로 if문 분리하기란?
키워드
- DRY, KISS 지침을 따른경우라도 코드는 여전히 혼란스러움
1. 다섯줄 제한(FIVE LINES)으로 지나치게 긴 메서드 식별하기란?
- 정의
- {} 제외 if, for, while, 세미콜론끝나는 모든것은 5줄이상이면 안됨 → 줄수 제한 자체가 중요
- 도우미 메서드 만들기
- 스멜
- 메서드가 길다는것 자체가 스멜
- 길다는것?
- 긴논리를 머리속에 담아야함
- 메서드는 한가지 작업만 해야한다
- 의도
- 장점
- 각 메서드의 이름으로 코드의 의도를 전달가능
- 5줄마다 주석을 넣는것과 같음
- 작은메스드 이름을 적절하게 붙이면 → 큰함수 이름 지정에 도움
- 장점
2. 세부사항을 보지 않고 코드 작업하기란?
- 형태살피기 → 그룹을 식별하기
- 그룹을 명확히 하기 위해 그룹이라 생각하는것에 ‘빈줄’을 추가함
- 추가한 빈줄에다가 ‘주석’을 달아줌
3. 메서드 추출(EXTRACT METHOD)로 긴 메소드 분해하기란?
정의
- 한 메서드의 일부를 취해서 자체 메서드로 추출함
절차
- 그룹식별(빈줄 주석 표시)
→ (빈메서드 만들기, 그룹위 빈메서드 호출, 빈메서드에 그룹 넣기, 컴파일)
→(호출하는 쪽에 오류발생시킴, 반환값 할당, 컴파일)
→ 호출시 인자오류 바로잡기
→ 빈줄 주석 제거
- 그룹식별(빈줄 주석 표시)
과정
주석제거 메소드 개선과 추출
function draw() { let canvas = document.getElementById("GameCanvas") as HTMLCanvasElement; let g = canvas.getContext("2d"); g.clearRect(0, 0, canvas.width, canvas.height); //(빈줄) -> 그룹식별을 위해 빈줄 추가 // Draw map -> 일시적 for (let y = 0; y < map.length; y++) { for (let x = 0; x < map[y].length; x++) { if (map[y][x] === Tile.FLUX) g.fillStyle = "#ccffcc"; else if (map[y][x] === Tile.UNBREAKABLE) g.fillStyle = "#999999"; else if (map[y][x] === Tile.STONE || map[y][x] === Tile.FALLING_STONE) g.fillStyle = "#0000cc"; else if (map[y][x] === Tile.BOX || map[y][x] === Tile.FALLING_BOX) g.fillStyle = "#8b4513"; else if (map[y][x] === Tile.KEY1 || map[y][x] === Tile.LOCK1) g.fillStyle = "#ffcc00"; else if (map[y][x] === Tile.KEY2 || map[y][x] === Tile.LOCK2) g.fillStyle = "#00ccff"; if (map[y][x] !== Tile.AIR && map[y][x] !== Tile.PLAYER) g.fillRect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE); } } // Draw player g.fillStyle = "#ff0000"; g.fillRect(playerx * TILE_SIZE, playery * TILE_SIZE, TILE_SIZE, TILE_SIZE); }
function draw() { let canvas = document.getElementById("GameCanvas") as HTMLCanvasElement; let g = canvas.getContext("2d"); g.clearRect(0, 0, canvas.width, canvas.height); // -> 빈줄과 주석 제거 (각 함수화 자체가 역할을 이미 함) drwaMap(g); drawPlayer(g); } //매개변수 에러 발생 -> 'npx tsc' 로 컴파일러로 알수 있음 function drwaMap(){ for (let y = 0; y < map.length; y++) { for (let x = 0; x < map[y].length; x++) { if (map[y][x] === Tile.FLUX) g.fillStyle = "#ccffcc"; else if (map[y][x] === Tile.UNBREAKABLE) g.fillStyle = "#999999"; else if (map[y][x] === Tile.STONE || map[y][x] === Tile.FALLING_STONE) g.fillStyle = "#0000cc"; else if (map[y][x] === Tile.BOX || map[y][x] === Tile.FALLING_BOX) g.fillStyle = "#8b4513"; else if (map[y][x] === Tile.KEY1 || map[y][x] === Tile.LOCK1) g.fillStyle = "#ffcc00"; else if (map[y][x] === Tile.KEY2 || map[y][x] === Tile.LOCK2) g.fillStyle = "#00ccff"; if (map[y][x] !== Tile.AIR && map[y][x] !== Tile.PLAYER) g.fillRect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE); } } } function drawPlayer(g: CanvasRenderingContext2D){ g.fillStyle = "#ff0000"; g.fillRect(playerx * TILE_SIZE, playery * TILE_SIZE, TILE_SIZE, TILE_SIZE); }
서로 다른 수준의 추상화가 섞여있는 문제를 찾아냄
말:
- 고급스러워 보이려고 노력하다가 어려움을 겪을 수 있는데, 보통은 그럴만한 가치가 없습니다
- 이 절차는 아무것도 손상시키지않습니다.
- 아무것도 고장내지 않았다는 확신이 완벽한 결과보다 더 가치가 있습니다
4. 호출 또는 전달, 한가지만할것(EITHER CALL OR PASS)으로 추상화 수준에 맞추기란?
- 정의
- 함수 내에서 객체(e.g. 배열)에 있는 메서드 호출하거나, 객체를 인자로 전달할 수 있음
- 즉 코드를 직접 조작하는 낮은 수준의 작업(e.g. 낮은 수준의 arr.length)과 다른 함수에 인자를 전달하는 높은 수준의 호출(e.g. 높은 수준의 추상화 sum(arr)) 공존
→ 메서드 이름사이 불일치
→ 가독성 하락 - (MO : 추상화 수준 높은것은 값을 미리 예측할수없는 즉 매개변수를 던져주는 방식 → 즉 미리 알수l없음)
- 스멜
- 매우 강력한 스멜
- 식별은 인자로 전달된 변수 옆 .(점)
- 의도
- 메서드 내부에 추상화 수준이 항상 동일하게 유지됨
function draw() {
let canvas = document.getElementById("GameCanvas");
//추출 했다고 하더라도 여기도 낮은수준 추상화임
let g = canvas.getContext("2d");
//현재 상태 -> 추상화 단계가 다름 (각 아래 1,2)
//1. 직접 조작하는 낮은수준 추상화
g.clearRect(0, 0, canvas.width, canvas.height);
//2. 다른함수에 인자를 전달하는 높은 수준의 추상화
drwaMap(g);
drawPlayer(g);
}
이런 경우 clearRect 를 추출하면되지만 다시 canvas.getContext 여기도 마찬가지임
→ 대신 최초 3줄을 추출하자
function createGraphics(){
let canvas = document.getElementById("GameCanvas") as HTMLCanvasElement;
let g = canvas.getContext("2d");
g.clearRect(0, 0, canvas.width, canvas.height);
return g; //return 해줌
}
function draw() {
let g = createGraphics(); //return 받아주기
drwaMap(g);
drawPlayer(g);
}
반복
//while 은 두개로 덩어리가 보임 while (inputs.length > 0) { let current = inputs.pop(); if (current === Input.LEFT) moveHorizontal(-1); else if (current === Input.RIGHT) moveHorizontal(1); else if (current === Input.UP) moveVertical(-1); else if (current === Input.DOWN) moveVertical(1); } for (let y = map.length - 1; y >= 0; y--) { for (let x = 0; x < map[y].length; x++) { if ((map[y][x] === Tile.STONE || map[y][x] === Tile.FALLING_STONE) && map[y + 1][x] === Tile.AIR) { map[y + 1][x] = Tile.FALLING_STONE; map[y][x] = Tile.AIR; } else if ((map[y][x] === Tile.BOX || map[y][x] === Tile.FALLING_BOX) && map[y + 1][x] === Tile.AIR) { map[y + 1][x] = Tile.FALLING_BOX; map[y][x] = Tile.AIR; } else if (map[y][x] === Tile.FALLING_STONE) { map[y][x] = Tile.STONE; } else if (map[y][x] === Tile.FALLING_BOX) { map[y][x] = Tile.BOX; } } } }
좋은 함수 이름의 속성 (도종환)
도 : 도메인에서 일하는 사람이 이해할 수있어야
종 : 정(종)직 해야함 → 함수의 의도를 설명
환 : 환(완)전해야함 → 함수의 모든것을 포괄해야함
구체적 방법?
- 그룹별 지배적인 단어가 무엇인지?
- 항상 나중에 함수가 더 작아졌을 때 이름개선여부 평가
//update 메소드 분리
function update() {
//각각은 복잡해서 이해하기 번거롭 -> 지배적인단어가 무엇?
// 각 input, Map -> 함수명 지정
handleInput();
updateMap();
}
function handleInput(){
while (inputs.length > 0) {
let current = inputs.pop();
if (current === Input.LEFT)
moveHorizontal(-1);
else if (current === Input.RIGHT)
moveHorizontal(1);
else if (current === Input.UP)
moveVertical(-1);
else if (current === Input.DOWN)
moveVertical(1);
}
}
function updateMap(){
for (let y = map.length - 1; y >= 0; y--) {
for (let x = 0; x < map[y].length; x++) {
if ((map[y][x] === Tile.STONE || map[y][x] === Tile.FALLING_STONE)
&& map[y + 1][x] === Tile.AIR) {
map[y + 1][x] = Tile.FALLING_STONE;
map[y][x] = Tile.AIR;
} else if ((map[y][x] === Tile.BOX || map[y][x] === Tile.FALLING_BOX)
&& map[y + 1][x] === Tile.AIR) {
map[y + 1][x] = Tile.FALLING_BOX;
map[y][x] = Tile.AIR;
} else if (map[y][x] === Tile.FALLING_STONE) {
map[y][x] = Tile.STONE;
} else if (map[y][x] === Tile.FALLING_BOX) {
map[y][x] = Tile.BOX;
}
}
}
}
5. if 문은 함수의 시작에만 배치로 if문 분리하기란? (너무 많은 일을 하는 함수 분리하기)
정의
- if문은 함수의 첫번째 항목이어야함
- If 문은 무언가를 확인하는 것이고 확인은 한가지 일이다 (아래 코드1 참고)
- 결국 if문은 함수의 첫번째 항목일수밖에 없고, 다른 일은 못하므로 if문은 유일해야함
- 또한 if문 {} 내 본문추출과 else 문과 분리하면 안됨 → 구조의 일부임
스멜
- 이 규칙은 함수가 한가지 이상의 작업 수행 막기위함
의도
- if문은 하나의 작업이다. else if 는 if문과 분리할수 없는 원자 단위임
if문은 함수의 시작에만 배치
function update() { while (inputs.length > 0) { let current = inputs.pop(); // 코드 1 // 조건문으로 무언가를 확인하는 것은 한가지의 일임 // -> 그러므로 함수의 첫번째 항목이어야하며, 유일한것이어야 함 if (current === Input.LEFT) moveHorizontal(-1); else if (current === Input.RIGHT) moveHorizontal(1); else if (current === Input.UP) moveVertical(-1); else if (current === Input.DOWN) moveVertical(1); }
function handleInputs(){ while (inputs.length > 0) { let current = inputs.pop(); handleInput(current) } } // 새로운 이름을 매개변수에 지정해서 가독성을 높일 수있음 //function handleInput(current: Input){ function handleInput(input: Input){ if (input === Input.LEFT) moveHorizontal(-1); else if (input === Input.RIGHT) moveHorizontal(1); else if (input === Input.UP) moveVertical(-1); else if (input === Input.DOWN) moveVertical(1); }
요약
다섯줄 제한(FIVE LINES)으로 지나치게 긴 메서드 식별하기란?
- 두가지 이상 작업 수행하는 메서드 식별 목적
세부사항을 보지 않고 코드 작업하기란?
- 덩어리로 보기
- 메서드 추출(EXTRACT METHOD)로 긴 메소드 분해하기란?
- (다섯줄제한, 추상화수준 맞추기, if문 함수시작에만 배치)의 결과 → 매개변수 이름 변경이점도 있음 → 가독성 향상
- 호출 또는 전달, 한가지만할것(EITHER CALL OR PASS)으로 추상화 수준에 맞추기란?
- 여러 수준의 추상화가 있는 메서드 식별에 도움이 됨 → 추상화 수준 맞추기 가능
- if 문은 함수의 시작에만 배치로 if문 분리하기란?
- 조건문 확인이 한가지의 작업임 → 메서드 추출
+: 마틴파울러 리팩터링과 추출하는 구조적 패턴이 비슷함 → 실무에서는 IDE도움을 받음 (단축키 촥촥촥)
'cleanCode > FiveLinesOfCode' 카테고리의 다른 글
[FiveLinesOfCode] 6장: 유사한 코드 융합하기 (1) | 2023.10.29 |
---|---|
[FiveLinesOfCode] 5장 유사한 코드 융합하기 (1) | 2023.10.29 |
[FiveLinesOfCode] 4장 타입코드 처리하기 (1) | 2023.10.08 |
[FiveLinesOfCode] 2장 : 리팩터링 깊게 들여다보기 (0) | 2023.10.03 |
[FiveLinesOfCode] 1장 : 리팩터링, 리팩터링 하기 (0) | 2023.10.03 |