글 작성자: 망고좋아
반응형

📖 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' 카테고리의 다른 글

[Clean Code] 의미 있는 이름  (0) 2022.04.29
[Clean Code] 깨끗한 코드  (0) 2022.04.23
[Clean Code] 클린 코드 시작  (0) 2022.04.22