[Clean Code] 함수
글 작성자: 망고좋아
반응형
📖 Clean Code - 함수
- 함수
📝 기억하고 싶은 내용
- 작게 만들어라! (p. 42)
- 함수에서 들여 쓰기 수준은 1단이나 2단을 넘어서면 안 된다. (p. 44)
- 한 가지만 해라! (p. 44)
- 따라서, 함수가 ‘한 가지’만 하는지 판단하는 방법이 하나 더 있다. 단순히 다른 표현이 아니라 의미 있는 이름으로 다른 함수를 추출할 수 있다면 그 함수는 여러 작업을 하는 셈이다. (p. 45)
- 함수 당 추상화 수준은 하나로!
- 함수가 확실히 ‘한 가지’ 작업만 하려면 함수 내 모든 문장의 추상화 수준이 동일해야 한다. (p. 45)
- 서술적인 이름을 사용하라! (p. 49)
- 함수가 하는 일을 좀 더 잘 표현하므로 훨씬 좋은 이름이다.
- 이름이 길어도 괜찮다. 겁먹을 필요 없다. 길고 서술적인 이름이 짧고 어려운 이름보다 좋다. 길고 서술적인 이름이 길고 서술적인 주석보다 좋다. 함수 이름을 정할 때는 여러 단어가 쉽게 읽히는 명명법을 사용한다.
- 이름을 붙일 때는 일관성이 있어야 한다. 모듈 내에서 함수 이름은 같은 문구, 명사, 동사를 사용한다.
- 함수 인수
- 함수에서 이상적인 인수 개수는 0개(무항)다. 다음은 1개(단항)고, 다음은 2개(이항)다. 3개 (삼항)는 가능한 피하는 편이 좋다. (p. 50)
- 부수 효과를 일으키지 마라! (p. 54)
- 명령과 조회를 분리하라!
- 함수는 뭔가를 수행하거나 뭔가에 답하거나 둘 중 하나만 해야 한다. 둘 다 하면 안 된다. 객체 상태를 변경하거나 아니면 객체 정보를 반환하거나 둘 중 하나다. 둘 다 하면 혼란을 초래한다. (p. 56)
- 오류 코드보다 예외를 사용하라! (p. 57)
- try/catch 블록은 원래 추하다. 코드 구조에 혼란을 일으키며, 정상 동작과 오류 처리 동작을 뒤섞는다. 그러므로 try/catch 블록을 별도 함수로 뽑아내는 편이 좋다. (p. 58)
- 반복하지 마라! (p. 60)
- 구조적 프로그래밍
- 데이크스트라는 모든 함수와 함수 내 모든 블록에 입구 entry와 출구 exit가 하나만 존재해야 한다고 말했다. 즉, 함수는 return 문이 하나여야 한다는 말이다. 루프 안에서 break나 continue를 사용해선 안 되며 goto는 절대로, 절대로 안 된다.
- 함수를 작게 만든다면 간혹 return, break, continue를 여러 차례 사용해도 괜찮다. (p.61)
- 함수를 어떻게 짜죠?
- 나는 그 서투른 코드를 빠짐없이 테스트하는 단위 테스트 케이스도 만든다.
- 그런 다음 나는 코드를 다듬고, 함수를 만들고, 이름을 바꾸고, 중복을 제거한다. 메서드를 줄이고 순서를 바꾼다. 때로는 전체 클래스를 쪼개기도 한다. 이 와중에도 코드는 항상 단위 테스트를 통과한다.
- 프로그래밍 언어라는 수단을 사용해 좀 더 풍부하고 좀 더 표현력이 강한 언어를 만들어 이야기를 풀어간다. 진짜 목표는 시스템이라는 이야기를 풀어가는 데 있다는 사실을 명심하기 바란다. (p.62)
🏷 요약
📕 함수의 매개변수는 2개 혹은 그 이하가 이상적
// Bad
function createMenu(title: string, body: string, buttonText: string, cancellable: boolean) {
// ...
}
createMenu('Foo', 'Bar', 'Baz', true);
// Good
type MenuOptions = { title: string, body: string, buttonText: string, cancellable: boolean };
function createMenu(options: MenuOptions) {
// ...
}
createMenu({
title: 'Foo',
body: 'Bar',
buttonText: 'Baz',
cancellable: true
});
📕 함수는 한 가지만 해야 합니다.
// Bad
function emailClients(clients: Client[]) {
clients.forEach((client) => {
const clientRecord = database.lookup(client);
if (clientRecord.isActive()) {
email(client);
}
});
}
// Good
function emailClients(clients: Client[]) {
clients.filter(isActiveClient).forEach(email);
}
function isActiveClient(client: Client) {
const clientRecord = database.lookup(client);
return clientRecord.isActive();
}
📕 함수가 무엇을 하는지 알 수 있도록 함수 이름을 지으세요.
// Bad
function addToDate(date: Date, month: number): Date {
// ...
}
const date = new Date();
// 무엇이 추가되는지 함수 이름만으로 유추하기 어렵습니다
addToDate(date, 1);
// Good
function addMonthToDate(date: Date, month: number): Date {
// ...
}
const date = new Date();
addMonthToDate(date, 1);
📕 함수는 단일 행동을 추상화해야 합니다.
- 재사용성과 쉬운 테스트를 위해서 함수를 쪼개세요.
// Bad
function parseCode(code: string) {
const REGEXES = [ /* ... */ ];
const statements = code.split(' ');
const tokens = [];
REGEXES.forEach((regex) => {
statements.forEach((statement) => {
// ...
});
});
const ast = [];
tokens.forEach((token) => {
// lex...
});
ast.forEach((node) => {
// parse...
});
}
// Good
const REGEXES = [ /* ... */ ];
function parseCode(code: string) {
const tokens = tokenize(code);
const syntaxTree = parse(tokens);
syntaxTree.forEach((node) => {
// parse...
});
}
function tokenize(code: string): Token[] {
const statements = code.split(' ');
const tokens: Token[] = [];
REGEXES.forEach((regex) => {
statements.forEach((statement) => {
tokens.push( /* ... */ );
});
});
return tokens;
}
function parse(tokens: Token[]): SyntaxTree {
const syntaxTree: SyntaxTree[] = [];
tokens.forEach((token) => {
syntaxTree.push( /* ... */ );
});
return syntaxTree;
}
📕 중복된 코드를 제거해주세요.
// Bad
function showDeveloperList(developers: Developer[]) {
developers.forEach((developer) => {
const expectedSalary = developer.calculateExpectedSalary();
const experience = developer.getExperience();
const githubLink = developer.getGithubLink();
const data = {
expectedSalary,
experience,
githubLink
};
render(data);
});
}
function showManagerList(managers: Manager[]) {
managers.forEach((manager) => {
const expectedSalary = manager.calculateExpectedSalary();
const experience = manager.getExperience();
const portfolio = manager.getMBAProjects();
const data = {
expectedSalary,
experience,
portfolio
};
render(data);
});
}
// Good
class Developer {
// ...
getExtraDetails() {
return {
githubLink: this.githubLink,
}
}
}
class Manager {
// ...
getExtraDetails() {
return {
portfolio: this.portfolio,
}
}
}
function showEmployeeList(employee: Developer | Manager) {
employee.forEach((employee) => {
const expectedSalary = employee.calculateExpectedSalary();
const experience = employee.getExperience();
const extra = employee.getExtraDetails();
const data = {
expectedSalary,
experience,
extra,
};
render(data);
});
}
📕 함수 매개변수로 플래그를 사용하지 마세요.
- 플래그를 사용하는 것은 해당 함수가 한 가지 이상의 일을 한다는 것을 뜻합니다.
- 함수는 한 가지의 일을 해야 합니다.
- boolean 변수로 인해 다른 코드가 실행된다면 그 함수를 쪼개도록 하세요.
// Bad
function createFile(name: string, temp: boolean) {
if (temp) {
fs.create(`./temp/${name}`);
} else {
fs.create(name);
}
}
// Good
function createTempFile(name: string) {
createFile(`./temp/${name}`);
}
function createFile(name: string) {
fs.create(name);
}
📕 명령형 프로그래밍보다 함수형 프로그래밍을 지향하세요
// Bad
const contributions = [
{
name: 'Uncle Bobby',
linesOfCode: 500
}, {
name: 'Suzie Q',
linesOfCode: 1500
}, {
name: 'Jimmy Gosling',
linesOfCode: 150
}, {
name: 'Gracie Hopper',
linesOfCode: 1000
}
];
let totalOutput = 0;
for (let i = 0; i < contributions.length; i++) {
totalOutput += contributions[i].linesOfCode;
}
// Good
const contributions = [
{
name: 'Uncle Bobby',
linesOfCode: 500
}, {
name: 'Suzie Q',
linesOfCode: 1500
}, {
name: 'Jimmy Gosling',
linesOfCode: 150
}, {
name: 'Gracie Hopper',
linesOfCode: 1000
}
];
const totalOutput = contributions
.reduce((totalLines, output) => totalLines + output.linesOfCode, 0);
📕 조건문을 캡슐화하세요
// Bad
if (subscription.isTrial || account.balance > 0) {
// ...
}
// Good
function canActivateService(subscription: Subscription, account: Account) {
return subscription.isTrial || account.balance > 0;
}
if (canActivateService(subscription, account)) {
// ...
}
📕 부정 조건문을 피하세요
// Bad
function isEmailNotUsed(email: string): boolean {
// ...
}
if (isEmailNotUsed(email)) {
// ...
}
// Good
function isEmailUsed(email): boolean {
// ...
}
if (!isEmailUsed(node)) {
// ...
}
📕 타입 체킹을 피하세요
// Bad
function travelToTexas(vehicle: Bicycle | Car) {
if (vehicle instanceof Bicycle) {
vehicle.pedal(currentLocation, new Location('texas'));
} else if (vehicle instanceof Car) {
vehicle.drive(currentLocation, new Location('texas'));
}
}
// Good
type Vehicle = Bicycle | Car;
function travelToTexas(vehicle: Vehicle) {
vehicle.move(currentLocation, new Location('texas'));
}
💡 오늘 깨달은 것
- 반복적인 작업이나 비즈니스 로직을 분리 또는 가독성을 높이기 위해 함수를 사용한다.
- 함수는 한 가지 기능만을 수행해야 한다는 것을 알면서 나도 모르게 2가지 일을 하는 함수를 짜는 것을 리팩터링 과정 중 발견할 수 있을 것이다.
- 이번 챕터를 공부하면서 조건문을 캡슐화하여라 라는 새로운 관점을 알게 되었다. 다음번에 이 점을 유의하면서 가독성 있는 함수를 작성해보자.
📌 참고
- Clean Code, 로버트 C. 마틴, 인사이트
- GitHub - 738/clean-code-typescript: 🚿 타입스크립트를 위한 클린코드 - 한글 번역판 🇰🇷
반응형
'프로그래밍 > Clean Code' 카테고리의 다른 글
[Clean Code] 의미 있는 이름 (0) | 2022.04.29 |
---|---|
[Clean Code] 깨끗한 코드 (0) | 2022.04.23 |
[Clean Code] 클린 코드 시작 (0) | 2022.04.22 |
댓글
이 글 공유하기
다른 글
-
[Clean Code] 의미 있는 이름
[Clean Code] 의미 있는 이름
2022.04.29 -
[Clean Code] 깨끗한 코드
[Clean Code] 깨끗한 코드
2022.04.23 -
[Clean Code] 클린 코드 시작
[Clean Code] 클린 코드 시작
2022.04.22