좋은 분기문(if) 작성법

분기문이 객체지향적 사고를 방해한다고 하였지만, 역으로 프로그래밍에서 논리 처리를 위해서 분기문은 필수로 필요합니다.

하지만 분기문을 적절하게 사용하지 못하면 가독성을 방해하고 유지보수 하기 힘든 코드로 만듭니다. 이와 관련해서 특히 분기문을 통해서 유효성 처리를 하는 경우 적절한 분기처리 숙어(idiom)를 알려드리겠습니다.

보호절 숙어

기존

void initialize() {
    if (!isInitialized()) {
        // 적절한 초기화 로직
    }
}

개선

void initialize() {
    if (isInitialized()) {
        return;
    }
    // 적절한 초기화 로직
}

기존과 개선을 비교하면 도찐개찐 인 거 같습니다. 하지만 두번째 예제를 보면 생각이 달라질 겁니다.

기존 - 중첩 if

void compute() {
    Server server = getServer();
    if (server != null) {
        Client client = server.getClient();
        if (client != null) {
            Request current = client.getRequest();
            if (current != null) {
                // 실제 처리할 로직
                processRequest(current);
            }
        }
    }
}

개선 - 중첩 if

void compute() {
    Server server = getServer();
    if (server == null)
        return;
    Client client = server.getClient();
    if (client == null)
        return;
    Request current = client.getRequest();
    if (current == null)
        return;
    // 실제 처리할 로직    
    processRequest(current);
}

기존과 개선의 차이가 느껴지십니까?

일반적으로 개발할 시 긍정적(유효한) 상황을 염두하고 개발을 진행하게 됩니다. 그래서 계속 적으로 유효성 체크 로직이 포함할 시에는 마치 계층(hierarchy) 구조를 보는 것 같습니다. - 기존

하지만 이를 역으로 부정적(유효하지 않는 상황) 상황을 염두하고 분기처리하면 선형(linear) 구조를 취합니다. - 개선

고로 더 나은 가독성과 구조를 제공하는 것입니다.

이를 보호절 숙어라고 부르는데 실제 처리할 로직이 처리되기 전에 유효하지 않는 상황으로 분기되면 해당 지역(메서드, 루프 블록 등)을 벗어나게 하는 것입니다. 보통 return을 하거나 예외(Exception)를 발생시킵니다.

그럼 loop 구문 내 분기는 어떻게 처리하면 될까요?

while (line = reader.readline()) {
    if (line.startsWith('#') || line.isEmpty())
        continue;
    // 실제 처리할 로직
}

continue를 이용하면 유사하게 처리할 수 있습니다.

가독성을 위한 if

조건문에서 인수의 순서

if (10 <= length)

// 아래가 더 낫다.
if (length >= 10)

좌변에 유동적인 값이나 표현을 넣고, 우변에 상수와 같은 고정값 표현을 넣어야지 가독성이 좋음

if/else 블록의 순서

  • 가능한 if에는 긍정의 조건을 넣어야지 가독성이 좋음.
  • 두 블록 중 간단한 블록을 먼저 if에 둠

더 흥미롭고, 확실한 것을 if절에 먼저 보이게 한다.

// 가능하면 긍정적인 조건 ex:hasAccount
if (...) {
    // 간단하고 긍정적인 내용
} else {
    /*
     * 상대적으로 복잡함
     * ...
     * ...
     */
}

단 보호절 숙어가 우선순위가 높음 if가 계층구조로 중첩될 거 같은데 부정의 조건을 넣어서 계층구조가 사라지면 이것이 더 낫다는 말임

삼항연산자

  • 간단한 구문일 경우 : 삼항연산자
  • 복잡한 구문일 경우 : if/else

삼항연산자는 코드 한 라인으로 적절한 가독성을 확보할 수 있을 때 사용하며, 만약 한 라인으로 처리하기 복잡할 경우에는 if/else를 쓰는 것이 훨씬 유리하다.

삼항연산자가 유리한 경우

String timeType = hour < 12 ? "am" : "pm";

if/else가 유리한 경우

String timeKor, timeEng;
if (hour < 12) {
    timeKor = "오전";
    timeEng = "am"
} else {
    timeKor = "오후";
    timeEng = "pm";
    callPmCustomAction();
}

복잡한 분기 추출

기존

    if(request.user.id==document.owner_id){
    //사용자가 이 문서를 수정할 수 있다.
    }
    ...
    if(request.user.id!=document.owner_id){
    //문서는 읽기 전용이다.
    }

개선

    final boolean isOwner = this.isOwner(request, document);
    if(isOwner){
    //사용자가 이 문서를 수정할 수 있다.
    }
    ...
    if(!isOwner){
    //문서는 읽기 전용이다.
    }
...
private boolean isOwner(Request request, Document document) {
    return request.user.id == document.owner_id;
}

재사용 되거나, 혹여 재사용되지 않더라도 분기절내의 복잡한 내용을 메소드로 표현하게 되면 더 나은 가독성을 얻을 수 있다.

드모르간 법칙

기존

if(!(existsFile && !isProtected))Error("...");

개선

if(!existsFile || isProtected)Error("...");

괄호 중첩이 적을 수록 가독성에 좋다.

복잡한 논리 가독성 향상

기존

public class Range {
    private int begin;
    private int end;
    ...
    // begin이나 end가 other에 속하는지 확인
    private void overlapsWith(Range other){
        return(begin>=other.begin&&begin<other.end)||
                (end>other.begin&&end<=other.end)||
                (begin<=other.begin&&end>=other.end);
    }
}

개선

...
    // begin이나 end가 other에 속하는지 확인
    private void overlapsWith(Range other){
        // 우리가 시작하기 전에 끝난다.
        if (other.end <= begin) return false;
        // 우리가 끝난 후에 시작한다.
        if (other.begin >= end) return false;
        // 겹친다.
        return true;
    }
...

무리하게 한 라인으로 논리를 표현하지 않고 가독성 확보를 위해 적절하게 여러 라인으로 분리한다. - 보호절의 그것과 유사하다 -

참고

  • 구현패턴[켄트 백] - 보호절
  • 읽기 좋은 코드가 좋은 코드다[더스틴 보즈웰, 트레버 파우커]

MJ

MJ
Backend 개발자 사람입니다. 어플리케이션의 복잡성을 다루는 DDD에 관심이 많습니다. 어제보다 더 나은 개발자가 되려고 항상 노력합니다.

spring boot 2.4.x 에서 openfeign + hystrix 통합하기

spring-boot 2.4.x spring-cloud 2020.x 의존성 상황에서 feign.hystrix.enabled=true가 안됨`feign.circuitbreaker.enabled=true` 로 바꿔보지만 openfeign과 hystr...… Continue reading

IDDD 14장. 애플리케이션

Published on June 19, 2018