함수

문지은·2021년 9월 28일
0

clean code

목록 보기
3/10

최대한 작게 만들기

함수는 최대한 작고 짧게 만들어야 한다.
if, else, while 문에 들어가는 블록은 한 줄이어야 한다. 그 한 줄은 함수를 호출한다.
또한 함수는 한가지 일만 해야 한다. 이는 섹션으로 나눌 수 없어야 한다는 것이다.

아래는 내가 mupol에서 사용했던 현 유저의 비디오 목록을 가져오는 서비스 메서드이다.

// 원본
public VideoPageDto getUserVideoList(Long userId, int pageNum) {
        PageRequest pageRequest = PageRequest.of(pageNum, 20);
		
        // 비디오 목록 가져오기
        VideoPageDto dto = new VideoPageDto();
        Page<Video> result = videoRepository.findAllByUserId(userId, pageRequest).orElseThrow();
        dto.setVideoList(result.getContent());

	// prev, next page 여부 구하기
        boolean prev = result.getNumber() - 1 >= 0 && result.getNumber() - 1 <= result.getTotalPages() - 1;
        boolean next = result.getNumber() + 1 >= 0 && result.getNumber() + 1 <= result.getTotalPages() - 1;

	// prev, next page 여부 set 하기
        dto.setHasPrevPage(prev);
        dto.setHasNextPage(next);

        return dto;
    }
    
// 작게 만들기
public VideoPageDto getUserVideoList(Long userId, int pageNum) {
        PageRequest pageRequest = PageRequest.of(pageNum, 20);

	Page<Video> userVideoList = getUserVideoList(userId, pageRequest);
        dto.setVideoList(userVideoList.getContent());

        dto.setHasPrevPage(isPrevExist(userVideoList));
        dto.setHasNextPage(isNextExist(userVideoList));

        return dto;
    }

함수 당 추상화 수준은 하나로!

추상화 수준이 높은 코드와 낮은 코드를 섞게 되면 읽는 사람은 혼란이 오게 된다.
내려가기 규칙을 사용하여 함수를 타고 타고 들어감에 따라 함수의 추상화 수준은 한 단계씩 낮아지게 코드를 짜는 것을 추천한다.

// 위의 코드를 추상화 수준이 높은 코드와 낮은 코드를 섞은 경우
public VideoPageDto getUserVideoList(Long userId, int pageNum) {
        PageRequest pageRequest = PageRequest.of(pageNum, 20);
	
    	// 추상화 수준이 높다 
	Page<Video> userVideoList = getUserVideoList(userId, pageRequest);
        dto.setVideoList(userVideoList.getContent());

        // 추상화 수준이 낮다
        boolean prev = userVideoList.getNumber() - 1 >= 0 && userVideoList.getNumber() - 1 <= userVideoList.getTotalPages() - 1;
        boolean next = userVideoList.getNumber() + 1 >= 0 && userVideoList.getNumber() + 1 <= userVideoList.getTotalPages() - 1;

        dto.setHasPrevPage(prev);
        dto.setHasNextPage(next);

        return dto;
    }

switch 문을 조심해라

switch문은 작게 만들기 어렵다. 그 이유는 다음과 같다.
1. 함수가 길다.
2. 한 가지 작업만 수행하지 않는다.
3. SRP를 위반한다. (코드를 변경할 이유가 여러개)
4. OCP를 위반한다. (새로운 유형이 추가되면 수정해야 함)

그럴 때는 switch문을 추상 팩토리를 이용해서 숨긴다.

// 수정 전
String title;
String body = null;
String senderName = sender.getUsername();
switch (type) {
    	case comment:
            title = senderName + "님이 게시물에 댓글을 작성하였습니다.";
            break;
        case like:
           title = senderName + "님이 회원님의 게시물을 좋아합니다.";
           break;
        case follow:
           title = senderName + "님이 회원님을 팔로우하였습니다.";
           break;
        case video_posted:
           title = senderName + "님의 영상 업로드 처리가 완료되었습니다.";
           break;
        case sound_posted:
           title = senderName + "님의 음성 업로드 처리가 완료되었습니다.";
           break;
	default:
    	   throw new IllegalArgumentException("invalid notification type");
        }
        
// 추상 팩토리 이용 후
public interface titleFactory 
{
    public String createTitle(TargetType type) throws IOException;
}

public class titleFactoryImpl implements titleFactory
{
    public String createTitle(TargetType type) throws IOException
    {
    	switch (type) {
    	case comment:
            return senderName + "님이 게시물에 댓글을 작```
코드를 입력하세요
```성하였습니다.";
        case like:
           returnsenderName + "님이 회원님의 게시물을 좋아합니다.";
        case follow:
           return senderName + "님이 회원님을 팔로우하였습니다.";
        case video_posted:
           return senderName + "님의 영상 업로드 처리가 완료되었습니다.";
        case sound_posted:
           return senderName + "님의 음성 업로드 처리가 완료되었습니다.";
	default:
    	   throw new IllegalArgumentException("invalid notification type");
    }
}

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

이름이 길어도 괜찮다. 서술적인 주석보다는 서술적인 이름이 낫다.

함수 인수

함수 인수는 작을수록 좋다. 특히 4개 이상의 인수는 특별한 이유가 있어도 사용하지 않는 것이 좋다.
불필요한 인수는 세부사항을 너무 많이 외부에 알려지게 한다.
테스트할 때도 인수마다 유효한 값으로 조합해서 테스트하기 어렵게 만든다.
출력 인수는 흔하지 않은 개념이기에 독자 해석에 어려움을 준다.

단항 인수

  1. 질문을 던지는 경우
    boolean isPrime(int n);
  2. 이벤트
    passwordAttemptFailedNtimes(int attempts);

위의 두가지 경우가 아니라면 단항 함수는 피하는 것이 좋다.

플래그 인수

플래그 인수는 추하다. 이 함수는 여러개를 처리한다고 공표하는 셈이기 때문이다.
플래그 인수를 쓰지 않고 나누는 것이 맞다.

이항 함수

이항 함수는 단항 함수보다 이해하기 어렵다. 또한 변수의 순서를 외워야 하는 어려움이 있다.

assertEquals(expected, actual);

// 이런 형태였다면 순서를 외우지 않아도 됐을텐데,,,
expected.assertEquals(actual);

묶을 수 있는 인수는 객체로 묶어서 넘기는 것이 이해하기 편하다.

가변적인 인수 개수

void monad(Integer... args);
void dyad(String name, Integer... args);
위와 같은 예시들이 있다.

동사와 키워드

키워드를 함수 이름에 사용해보자. 독해가 더 쉬워진다.

assertEquals(expected, actual);

// 이런 형태였다면 더 이해하기 쉬웠을텐데,,,
assertExpectedEqualsActual(expected, actual);

부수효과를 일으키지 말아라!

함수는 함수 이름에 정의된 일만 처리해야 한다. 만약 아니라면 함수 이름을 바꾸는 편이 낫다.

출력 인수

출력 인수를 두는 것은 정말 부자연스러운 행위이다. 만약 footer를 덧붙여서 report를 반환하는 함수가 있다고 해보자. 그러면 아래 편이 훨씬 독해가 편하다.

// 출력 인수 o
public void appendFooter(StringBuffer report);

// 출력 인수 x
report.appendFooter();

명령과 조회를 분리하자

// set으로 명령, boolean으로 조회를 둘 다 하고 있다.
public boolean set(String attribute, String value);

// 명령과 조회를 분리한 경우
if (isExist("name"))
	set("name", "hi");

오류 코드보다 예외

오류 코드를 사용하면 바로 오류 코드를 처리해줘야 하기 때문에 코드가 더러워진다.
예외를 사용해서 던져준 뒤에 예외를 모아서 핸들러에서 처리하는 편이 깔끔하다.

Try/Catch 블록 뽑아내기

Try/Catch 블록은 추하다.

// 따로 뽑아내는 것이 보기 편하다.
try
{
	doSomething();
}
catch(Exception e)
{
	// 오류 처리도 한가지 작업이므로 오류 처리하는 함수를 만드는 편이 좋다.
	errorHandling(e);
}

구조적 프로그래밍

큰 함수에서 return, break, continue를 사용하는 것을 피하자.
작은 함수는 이점을 볼 수 있다.
goto는 무슨 상황이든 피하자.

profile
백엔드 개발자입니다.

0개의 댓글