규칙 - 왜 다섯 줄인가?
이 책에서는 근본적인 규칙으로 다섯 줄 제한을 소개합니다.
메서드는 {와 }를 제외하고 5줄 이상이 되면 안된다고 이야기 합니다.
아래 isTrue() 메서드는 4줄 입니다.
function isTrue(bool: boolean) {
if (bool)
return true;
else return false;
}
메서드가 길면 한 번에 긴 메서드의 모든 논리를 머릿속에 담아야하기 때문에 작업하기가 어렵습니다.
또한 메서드 이름만 보고 어떤 역할을 하는지 유추가 되기 때문에 주석을 넣는 것과 같은 효과를 낼 수 있습니다.
draw() 메서드는 매우 길지만 내부를 잘 보면 map을 그리는 영역과 player를 그리는 영역이 존재하고 있습니다. 우리는 메서드 추출 이라는 리팩터링을 통해서 함수의 라인 수를 줄일 수 있습니다.
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);
}
drawMap과 drawPlayer로 메서드를 추출 했습니다.
function draw() {
let canvas = document.getElementById("GameCanvas") as HTMLCanvasElement;
let g = canvas.getContext("2d");
g.clearRect(0, 0, canvas.width, canvas.height);
drawMap(g);
drawPlayer(g);
}
function drawMap(g: CanvasRenderingContext2D) {
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);
}
메서드 추출
이 책에서는 직접 손으로 하는게 안전한 방법이라고 소개하고 있는데 사람의 손을 타면 버그가 발생할 수 있는 여지가 생깁니다. IDE에서 제공하는 기계적인 메서드 추출 하는 것을 추천합니다.
메서드 추출을 하다 보면 인스턴스의 값을 변경하는 케이스가 생기는데 파라미터로 전달한 인스턴스를 변경하는 것 보다 메서드 내에서 새로운 인스턴스를 만드는 deep copy를 진행하고 이 인스턴스를 리턴하는 방식이 안전한 방식입니다.
규칙 - 호출 또는 전달, 한 가지만 할 것
함수 내에서는 객체에 있는 메서드를 호출하거나 객체를 인자로 전달할 수 있지만 둘을 섞어 사용해서는 안 됩니다.
function average(arr: number[]) {
return sum(arr) / arr.length;
}
average 메서드는 sum() 메서드에 객체를 전달하는 것과 arr.length 처럼 객체에 있는 메서드를 호출하는 두가지 작업을 섞어서 하고 있습니다.
function average(arr: number[]) {
return sum(arr) / size(arr);
}
메서드 내에서는 동일한 추상화 수준을 유지해야한다고 합니다. 이렇게 arr을 전달하기만 할 것인지, arr객체의 메서드를 호출만 할 것인지 정해야 합니다.
이 책 예제에 있는 작업 중에 g를 만드는 작업인 createGraphics()함수를 만들어서 내부 추상화 수준을 동일하게 유지할 수 있습니다.
function createGraphics() {
let canvas = document.getElementById("GameCanvas") as HTMLCanvasElement;
let g = canvas.getContext("2d");
g.clearRect(0, 0, canvas.width, canvas.height);
return g;
}
function draw() {
let g = createGraphics();
drawMap(g);
drawPlayer(g);
}
function drawMap(g: CanvasRenderingContext2D) {
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);
}
좋은 함수 이름의 속성
좋은 이름이 가져야 할 몇 가지 속성은 아래와 같습니다.
- 함수의 의도를 잘 설명해야 합니다.
- 함수가 하는 모든 것을 담아야 합니다.
- 도메인에서 일하는 사람이 이해할 수 있는 단어를 사용해야 합니다.
규칙 - if 문은 함수의 시작에만 배치
if 문이 있는 경우 해당 if 문은 함수의 첫 번째 항목이어야 합니다.
if는 무언가를 확인하는 한 가지 일을 뜻하는 것으로 함수는 한 가지 일을 해야하기 때문에 함수를 추출하는 것이 맞습니다.
function reportPrimes(n: number) {
for (let i = 2; i < n; i++)
if (isPrime(i))
console.log(`${i} is prime`);
}
reportPrimes()는 1. 숫자를 반복한다 2. 숫자가 소수인지 확인한다 와 같은 두 가지 작업을 진행하고 있습니다.
function reportPrimes(n: number) {
for (let i = 2; i < n; i++)
reportIfPrime(i);
}
function reportIfPrime(n: number) {
if (isPrime(n))
console.log(`${n} is prime`);
}
이런 if 문의 메서드 추출은 else if와 else까지를 하나의 작업으로 보고 추출을 해야 합니다.
'개발 > 리팩터링' 카테고리의 다른 글
파이브 라인스 오브 코드 - 타입 코드 처리하기 (1) | 2023.07.09 |
---|---|
파이브 라인스 오브 코드 - 리팩터링 깊게 들여다보기 (0) | 2023.06.19 |
파이브 라인스 오브 코드 - 리팩터링 리팩터링하기 (0) | 2023.06.11 |