CS/클린코드(cleancode)

3장. 함수

rachel_13 2022. 10. 30. 02:16

1.  작게 만들어라.

  • if/else 문 또는 while 문에 들어가는 블록은 한 줄 이면 충분하다.
  • - 대부분 block 에서 함수 호출 문을 사용한다. - 중첩구조가 생길 만틈 함수 구조가 복잡해서는 안된다.

2.  한 가지만 해라!

익히 들었던 말이다. 함수는 오로지 “한 가지의 기능”만 한다.

그 한 가지만을 잘하는 도록 만드는 것의 우리의 목표이다.

🤔 여러 조건문이 필요한 경우에는 어떻게 하나요?
- 단순 다른 표현이 아니더라도, 의미있는 이름으로 지은 다른 함수를 호출/추출할 수 있다면, 그 함수는 여러 작업을 하는 셈이다.

 

3.  위에서 아래로 읽을 수 있도록

  • Top-Down 방식
  • 한 가지 의미를 갖는 함수를 만들어야 함수를 위에서 아래로 읽어 내려갈 수 있다

4.  Switch문

switch문은 n가지 일을 처리하기 때문에 ‘한 가지’ 일만 작업하도록 만드는게 어렵다.

//직원 유형에 따라 다른 값을 계산해 반환하는 함수
public Money calculatePay (Employee e)
throws InvalidEmployeeType {
	switch (e.type) {
		case COMMISSIONED:
			return calculateCommissionedPay(e);
		case HOURLY:
			return calculateHourlyPay(e);
		case SALARIED:
			return calculateSalariedPay(e);
		default:
			throw new InvalidEmployeeType(e.type);
	}
}

문제점

1. 함수가 길다. -예를 들어 새 직원 유형이 생기면, 추가해야 한다.

2. 한가지 작업만 수행하지 않는다.

3. SRP를 위반한다.(단일 책임 원칙) -> 코드를 변경해야 할 이유가 많다.

(참고 : https://yoongrammer.tistory.com/96)

4. OCP를 위반한다. (개방 폐쇄 원칙) -> 새 직원 유형을 추가할 때마다 코드를 변경해야 한다.

(참고 : https://nesoy.github.io/articles/2018-01/OCP)

 

5. 서술적인 이름을 사용하라!

“코드를 읽으면서 짐작했던 기능을 각 루틴이 그대로 수행한다면 깨끗한 코드라 불러도 되겠다.” - 워드

나는 항상 이름이 긴 걸 꺼려했던 것 같다. 사람들이 이 이름이 너무 길어서 불평하면 어쩌지? 라고 걱정을 했었던 것다.

그런데, 괜히 이름 줄여서 무슨뜻인지 해석하게 만드는 것보다는 서술적으로 이름을 만들어서 이해하기 쉽도록 만드는 것이 더 중요한 것 같다.

  • 대신 이름을 붙일 땐, 일관성 있게 사용할 것
  • - 모듈 내 함수이름은 같은 문구, 명사, 동사를 활용한다.

 

6. 함수 인수

오.. 이 장은 적잖은 충격이다. 인수까지 적은게 좋다고?

인수가 들어가게 되면, 우리는 함수 뿐만 아니라 인수가 무슨 의미인지도 들여다 봐야 한다.

또한, 이 책에서 언급한 것처럼 “추상화”의 정도가 다를 수 있다.

최선은 입력 인수가 없는 경우, 차선은 입력 인수가 1개 뿐인 경우이다.

6-1. 많이 쓰는 단항 형식

🤔 인수가 1개 이상이 되는 경우는 어떤 경우일까?

  • 인수에 질문을 던지는 경우
  • boolean fileExists('MyFile')
  • 인수로 변환해 결과를 반환하는 경우
  • inputStream fileOpen('MyFile') // String 형의 파일이름을 inputStream 형으로 변환
  • 이벤트 함수
    • 입력 인수만 있음. 출력 인수 x
    • 프로그램은 함수 호출을 이벤트로 해석한다. → 따라서 인수로 시스템 상태를 바꿈
    passwordAttemptFailedNtimes(int attempts)
    

6-2. 플래그 인수

함수로 부울 값을 넘기는 것 ❌

ex) render(true)

대놓고 한 함수에서 여러 기능을 처리하겠다고 공표하는 것과 마찬가지이기 때문,

 

6-3. 이항함수

인수가 2개이면, 당연히 1개일 때보다 이해하기 어렵다.

//AS-IS
writeField(outputStream, input)

//TO-BE
public class outputStream {
	public writeField (String name){}
		....
}

outputStream.writeFiled(name)

6-4. 삼항 함수

6-5. 인수 객체

6-6. 인수 목록

  • 인수 개수가 가변적인 함수

6-7. 동사와 키워드

 

7. 부수효과를 일으키지 마라!

public boolean checkPassword(String userName, String Password) {
	User user = UserGateway.findByName(userName);
	if(user != User.NULL){
		...
		if("Vaild Password".equals(phrase)){
			Session.initialize();
			return true;
		}
	}
}

checkPassword 만 보면, 패스워드를 확인하는 함수로 보인다.

그러나 이 함수에서 부수효과는 바로 Session.intialize() 이 부분이다.

세션을 초기화하는 소스코드인데, 이름만 봐서는 알 수 없다. 자칫하면 함수를 남발했다가, 세션을 초기화 할 수도 있는 것..

 

8. 명령과 조회를 분리하라!

  • 함수의 기능 : 무언가를 수행한다. or 무언가에 답한다.

명령과 조회기능이 한 함수에 혼재되어 있으면, 각자 해석하는 의미가 달라질 수 있다.

예)

//AS-IS
if(set("username", "Tom"))

//TO-BE
if(attributeExists("username")){ //조회
	setAttribute("username", "Tom") //명령
}

 

9. 오류코드보다는 예외를 사용하자

어떤 함수가 오류코드를 반환하면, 이 함수의 호출자는 오류 코드를 곧바로 처리한다.

이럴 때는 오류코드 대신 예외 코드를 사용하면 원래 코드에서 분리되므로, 코드 자체가 깔끔해진다.

  • 이 때 try/catch 블록문은 별도 함수로 빼 두는 것이 조금 더 깔끔하겠다.

예)

AS-IS : 정상 동작과 오류 처리동작을 분리하지 않은 경우

try{
	deletePage(page);	
	registry.deleteReference(page.name);
	configKeys.deleteKey(page.name.makeKey());
}catch(Exception e){
	logger.log(e.getMessage());
}

TO-BE : 정상 동작과 오류 처리 동작을 분리한 경우

public void delete(Page page){
	try {
		deletePageAndAllReferences(page);
	}
	catch(Exception e){
		logError(e);
	}
}

private void deletePageAndAllReferences(Page page) throws Exception {
	deletePage(page);
	registry.deleteReference(page.name);
	configKeys.deleteKey(page.name.makeKey());
}

private vodi logError(Exception e){
	logger.log(e.getMessage());
}

 

10. 반복하지 마라!


결론

코드도 곧 언어이다. 함수는 그 안에서 동사이며, 클래스는 명사이다.

프로그래밍 기술은 어떻게 더 잘 언어를 풀어써나갈지 고민하는 그야말로 글쓰기 스킬이다.

내가 작성하는 함수가 분명하고 명확한 의미를 전달하며 한 편의 스토리가 짜여지듯 흐름대로 흘러갈 수 있느냐에 대해 끊임 없이 고민하는 과정이다.

처음부터 잘 할 수 없지만, 지금 작성해 둔 코드들을 하나씩 살펴보며, 중복된 반복문을 없애고, 들여쓰기를 다듬고, 이름을 이해하기 쉽게 바꾸고, 메서드를 줄이며 순서를 바꿔나가는 등 하나씩 다듬어 보는게 프로그래머의 과제가 아닐까 싶다.