정리, 기록

좋은 코드를 작성하는 습관

mopil 2023. 12. 23. 02:37
반응형

토스뱅크에서 9개월간 일하면서, 필자는 레거시 코드를 코프링 환경으로 내재화하는 작업을 진행했다.

 

그 과정에서 "왜 이렇게 짰을까?"하는 외부 솔루션 코드도 많았고, 매우 우아하게 짠 코드도 체험했다.

 

이를 통해  느낀 "좋은 코드"에 대한 정의와 이를 위해서 길들이면 좋은 습관을 나름 주관적으로 정리해보고자 한다.

 

(육각형 개발자, 이펙티브 코틀린 등 여럿 개발 저서들로 부터도 얻은 인사이트도 자연스레 녹아있음을 알린다.)

 

물론 아직 1년도 안 된 햇병아리 개발자의 주관적인 의견이라는 점도... 어느정도 감안하면 좋을 것 같다. 그리고 또 어찌 보면 되게 당연한 이야기를 하는 것일 수도 있을 것이다.

 

# 좋은 코드란?

필자가 생각하는 좋은 코드는 크게 

 

1. 히스토리를 모르는 그 누구라도 코드만 보고도 전반적인 플로우와 로직을 이해할 수 있는 코드 (가독성 측면)

2. 버그가 적고, 발생하더라도 쉽게 추적하여 수정할 수 있는 그리고 변경에 유연한 코드 (엔지니어링 측면)

 

두 가지라고 생각한다.

 

이는 유지보수와 생산성과도 직접적으로 연결된다.

회사에서는 많은 엔지니어들과 협업하여 코드를 작성하고, 내가 짠 코드가 아니더라도 유지보수를 해야 하는 경우가 많다.

 

내가 짠 코드를, 다른 동료가 수정해야하는 경우도 빈번하다.

 

개발자는 엔지니어링 오퍼레이션 비용을 낮추면서 (유지보수 비용) 개발 생산성을 높여야 한다. 

 

이를 위해서라면 미래의 나를 위해서, 다른 동료를 위해서라도 "좋은 코드"를 작성하도록 노력해야 한다.

 

지금 당장은 피부에 와닿지 않더라도, 언젠가는 체감하는 시점이 올 것이다. 필자도 그랬다. 그래서 여기서는, 지금은 느끼지 못하더라도 좋은 코드를 작성할 수 있게 하는 필자가 생각하는 주관적인 습관들을 소개해 보고자 한다.

 

 

먼저 1. 누구라도 이해가 쉬운 코드를 작성하는 습관에 대해 소개한다.

 

필자는 이를 달성하기 위해 처음 보는 사람이라도 물어보지 않고, 비즈니스 로직을 이해할 수 있는 코드를 작성하는 것에 초점을 맞춘다.

 

즉, 커뮤니케이션 비용을 줄이는 방향으로 논의해 본다.

 

# 길더라도 의미가 직관적인 변수, 함수명을 사용하자

필자는 아무리 길더라도 의미 전달에 더 중점을 둔다.

 

변수명은 해당 변수가 어떤 타입인지 이름만 보고 유추할 수 있도록 짓는 것이 좋다.

 

isForiegner vs. foreigner

 

외국인인지 아닌지 여부를 나타내는 변수를 만들 때, 다음 둘 중 전자가 boolean일 것이라는 느낌을 준다.

 

만약 경로를 인자로 넘겨야 한다면, 

weight: String 보다는 weightFilePath: String이 훨씬 직관적일 것이다.

 

 

함수는 그 행동을 명확하게 하도록 이름을 짓는 게 좋다.

 

유저가 존재하지 않으면 예외를 던지는 함수는,

 

getUser()보다는 getUserOrThrow()가 훨씬 직관적이다.

 

 

# 때로는 한글 변수, 함수명도 좋다.

"외국인 출입국 사실 증명서 발급 여부"라는 변수를 영어로 만든다고 생각하면 벌써부터 막막하다.

 

(isForeignCustomerCountryEntranceFactDocumentPublished...?)

 

영어로 짓더라도 이를 단번에 이해하는 것은 어려울 수도 있다.

 

이런 경우는 한글 변수명이 훨씬 직관적일 것이다.

 

# 불변 객체를 디폴트로 사용하자

코틀린의 경우 val로 모든 변수를 선언하도록 하자. val로 안 되는 경우만 var를 쓴다.

 

var로 변수를 선언하면, 비즈니스 로직이 길어질수록 변경 지점을 추적하기 어려워지고 이는 곧 유지보수성을 낮추는 역할을 한다.

 

 

# 복잡한 비즈니스 로직이면 주석을 최대한 활용하자

코드만 읽고 이해가능한 코드를 작성하는 것이 베스트이다. 하지만 주석을 아예 사용하지 말라는 극단적인 이야기는 아니다.

 

비즈니스 로직은 대부분 분기가 많고, 코드 상으론 이해하기 힘든 부분이 존재한다. 이런 경우 주석을 활용하는 것이 히스토리를 모르는 사람에게 이해를 돕는데 획기적인 역할을 할 것이다.

 

예를 들어, 해당 고객이 비거주자인지 아닌지 판단하는 로직이 있다고 가정하고 다음과 같이 되어있다고 해보자.

 

val isNotResidentInKorea = checkIfResidenceInKorea(user)


fun checkIfResidenceInKorea(user: User, skip: Boolean = true): Boolean {
    if (skip) return false
    // .. logics

}

 

 

그리고 비즈니스 로직상, 현재 우리 서비스는 비거주자를 취급하지 않으므로 해당 로직은 취급할 때까지 무조건 false로 반환할 것이라는 요건이 있다고 해보자.

 

이 코드를 처음 보는 사람은 이런 구체적인 비즈니스 요건이 있음을 알기 어렵다.

 

이런 경우 다음과 같이 주석으로 표기해 주면 추가 커뮤니케이션 비용 없이 바로 이해할 수 있을 것이다.

 

 // TODO : 비거주자는 취급하지 않아서 skip 대상

 

 

# 추상화를 많이 하라

함수 추출하기

대표적인 예로, 한 비즈니스 로직을 수행하는 함수에서 내부적으로 여럿 다른 일을 한다면, 이를 별도의 함수로 추출하는 것이 훨씬 가독성이 좋다.

 

fun makeCoffee(money: Int) {
    // 돈이 잘 들어왔는지 검증
    
    // 디비에 남아있는 커피 조회
    
    // 커피 만들기
    
    // 커피가 다 만들어졌다는 이벤트 발행
    
    // 커피 리턴
    
   }

 

위 함수의 총라인이 70줄이라 가정해 보면 처음 보는 사람이 이 코드를 이해하는 것은 엄청 부담될 것이다.

 

 

fun makeCoffee(money: Int) {
    validateMoney(money)
    
    checkCoffeeIfRemain()
    
    val coffee = makeCoffee()
    
    publishFinishMakeCoffeeEvent()
    
    return coffee
    
   }

 

이렇게 각 부분을 함수로 빼서 추상화하여 캡슐화하면 보다 가독성이 좋아진다. 설명을 하는 주석도 제거할 수 있다.

 

 

객체 위임하기

복잡한 검증로직을 5개정도 수행하는 로직이 비즈니스 로직에 있다고 해보자. 이러면 별도의 Validator 객체를 생성하여 위임하는게 가독성이 훨씬 좋다.

 

당연히 너무 불필요하게 많이 추상화하면 오히려 가독성을 떨어뜨릴 수도 있음을 명심하자.

 

 

# 예외 처리는 별도로 위임하고, try catch는 꼭 필요한 경우만 쓰자

우아하게 예외 던지기

너무 포괄적인 예외를 던지지 말자. (Exception, RuntimeException 등)

 

예외는 예외 명칭만으로도 어떤 오류인지 파악하게끔 하는 것이 좋다.

 

그리고 과도한 커스텀 예외보단, 모든 개발자가 직관적으로 알 수 있는 예외

(http 상태코드에 따른 예외나, 기본 예외 IllegalArgumentException 등)를 사용하는게 좋다.

 

throw CoffeeException("커피가 존재하지 않습니다.") // bad
throw NotFoundException("커피가 존재하지 않습니다.") // good

throw CoffeeException("돈은 음수가 될 수 없습니다.") // bad
throw IllegalArgumentException("돈은 음수가 될 수 없습니다.") // good

 

예외를 잡아서 다시 예외를 던지는 구문은 지양하자. 

이는 코드를 복잡하게 만든다.

try {
    // logics..
} catch (e: Exception) {
    log.warn("말뭉치 JSON 파일 읽기 실패 : ${e.message}")
    throw IllegalArgumentException("말뭉치 JSON 파일 읽기 실패 : ${e.message}")
}

 

이런 경우 포괄적인 Exception보다는, 해당 로직에서 발생할 수 있는 특정 예외만 잡도록 핸들러를 만드는 것이 좋다.

 

애초에 위 코드는 불필요한 try catch다.

 

 

예외처리 위임하기

try catch는 그 자체만으로 들여쓰기가 들어가서, 사용 안 하는 것 보다 가독성이 좋지 못 하다.

 

스프링은 ControllerAdvice로 위임할 수 있다.

만약 다른 프레임워크라면, 예외 처리를 전담하는 객체를 만들어서 위임하자.

 

 

try catch가 꼭 필요한 경우

try catch는 공통 예외 처리와는 다르게 특별하게 처리해야하는 경우만 선택적으로 사용하도록하자.

 

예를 들어, FeignException (외부 API call 실패)가 발생했을 때 모두 ControllerAdvice에서 예외처리를 하게끔 되어있다고 해보자.

 

그리고 해당 경우는 실패하면 그냥 빈 리스트를 내려야한다.

try {
    // logics..
} catch (e: FeignException) {
     return emptyList()
}

 

이러한 특수한 경우만 try catch를 사용하도록 하자.

 

이럴 땐 사용하는 부분을 메인 비즈니스 로직에서 분리하여 별도의 메서드로 추출하는 것도 좋다.

 

 

# if else는 간단한 분기 부터 체크, 되도록 when(switch) 쓰기

조건을 분기는 간단한 조건을 먼저 수행하자

    // bad
    if (user != null) {
        // do something 300 lines
    } else {
        throw IllegalArgumentException("유저가 없습니다.")
    }
    
    // not good
    if (user == null) {
        throw IllegalArgumentException("유저가 없습니다.")
    } else {
        // do something 300 lines
    }
    
    // good
    if (user == null) {
        throw IllegalArgumentException("유저가 없습니다.")
    }
    // do something 300 lines

 

마지막 경우 처럼 먼저 끝나는 간단한 분기를 먼저 체크하는 것이 가독성이 좋다.

 

 

if else 보다는 switch, when

취향 차이일 수도 있긴 하지만, 필자는 보통 if else 중첩보다는 when(switch)가 더 가독성이 좋다고 생각한다.

 

 

 

# DTO를 적극 사용하자

코틀린의 경우 Pair, Triple은 사용을 자제하자.

 

    return Pair(true, false) // bad
    
    return UserStatusDto(
        isLogin = true,
        isLogout = false
    ) // good

 

위 두 코드 중 아래 부분이 좀 더 길어지졌지만, 훨씬 직관적이다.

 

만약 파이썬인 경우, 디비에서 가져온 record들을 딕셔너리로 받는 것 보다 DTO로 래핑해서 받는게 훨씬 타입 안정적이고 좋다.

 

 

# enum을 적극 활용하자

코드값 보다는 직관적인 명칭으로

    if (boilerType == 9) {
        // something
    } // bad
    
    if (boilerType == GAS_BOILER.code) {
        // something
    } // good

 

request param은 될 수 있는 값만 enum으로 제한하기

@GetMapping("/{id}")
fun getUser(
    @RequestParam type: String // bad "admin", "normal"
    @RequestParam type: UserType // good UserType.ADMIN, UserType.NORMAL
)

 

위 두 가지만 지켜도 처음 코드를 읽는 개발자는 코드의 의도를 훨씬 빠르고 직관적으로 파악할 수 있을 것이다.

 

 

# 사용하지 않는 코드는 과감히 정리하자

사용하지 않는 코드를 주석 처리하고 내버려두는 것은 최악이다. 

 

요건이 변경되어 사용하지 않게 된 코드를 남겨두는 것도 좋지 못하다.

 

API까지 나온 상태이면 IDE의 도움으로는 이를 코드레벨에서 사용하고 있는지, 아닌지를 확인할 수 없다.

 

이런 코드는 모두 제거하도록 하자.

 

단, 추후 요건 변경에 유연하게 대응하기 위해서 만들어 둔 코드는 예외 대상이다.

(ex. 목록 조회 데이터 증가에 따라 커서 페이징, 일반 페이징 두 가지 버전을 만들어 뒀는데 MVP에서는 일반 페이징만 사용하고 커서는 사용하지 않음)

 

이런 경우 // TODO 를 통해 주석을 남겨두는 것이 좋다.

 

주의할 점은 삭제할 때 사용하는 곳이 없는지 꼭 더블체크하자

 

 

 

# API 문서화에 최대한 많은 정보를 담도록 하자

필자는 Swagger를 사용해서, request param의 nullable 여부 등을 모두 빠짐없이 표기한다.

 

이는 프론트 개발자와의 불필요한 커뮤니케이션을 없애준다.

 

특히 아래 두 가지를 꼭 지키자.

  • non-nullable과 nullable 필드 구분 표시
  • 여러 가짓수를 받는 건 문자열보다는 enum으로 가짓수 제한

 

# 주기적으로 리팩토링을 하자

코드를 작성하는 것은 글을 쓰는 것과 동일하다고 생각한다.

 

처음부터 완벽한 글을 쓰는 것은 어렵고, 초안 보다 첨삭을 거칠 수록 그 퀄리티가 올라간다. 코드도 동일하다.

 

여유가 생길 때 마다 코드를 리팩토링 하는 습관을 가지면 좋은 것 같다.

 

내가 성장함에 따라 내가 짠 코드가 레거시 하게 느껴지는 순간이 있을 것이다. 이런 경우 외면하지 말고 적극적으로 리팩토링 하도록 하자.

 

내가 아무런 도메인 지식이 없다고 가정하고 코드를 훑어보는 것도 좋은 방법이다.


 

다음으로는 2. 버그가 적고, 나더라도 수정할 곳을 찾기 쉬운 코드를 작성하는 습관들에 대해 소개한다.

 

# 변수의 스코프를 최소화하자

변수는 스코프를 벗어나면 GC 대상이 된다.

 

리스트와 같은 메모리를 많이 잡아먹을 수 있는 것들 (특히나 디비에서 꺼내와 리스트로 넣는 것)은 스코프를 최소화하는 것이 좋다.

 

이러한 습관은 성능 향상에도 연결될 수 있다.

 

# 문자열보다는 타입 세이프하게 사용하자

클래스 프로퍼티 이름을 어딘가 사용해야한다면, 문자열보다는 ::class.필드명.name으로 접근하자

 

코틀린에선 다음과 같이 사용할 수 있다.

    map["userId"] // bad
    map[UserDto::class.userId.name] // good

 

이러면 훨씬 타입 세이프하고, 필드명을 변경할 때 IDE의 일괄 리팩토링 기능도 활용할 수 있다.

 

# null 처리는 최대한 보수적으로, 확실하게 하자

 

다음과 같은 코드에서 NPE가 발생하면 추적하기 매우 곤란하다.

person!!.name!!.firstName!!.split(",")

 

우리는 NPE를 최대한 발생 안 시키게 해야 하며,

 

비즈니스 로직상으로도 절대 null이 될 수 없는 경우라도 null 처리를 확실하게 하는 것이 좋다.

 

val user = getUserById(userId)

// 비즈니스 로직상 유저 이름이 null이 될 수 없음
user.name!! // bad

user.name ?: throw IllegalArgumentException("[$userId] - 유저 이름 변경 오류 ::  유저의 이름이 존재하지 않습니다.") // good

 

비즈니스 로직은 항상 변화한다. 그리고 항상 예외 케이스는 존재한다.

 

만약 다른 시스템 오류로 유저의 이름이 null로 저장됐다면? 그리고 user.name을 사용하는 부분이 매우 많다면?

 

non-null is null 에러 발생 지점을 찾기 위해 엄청난 삽질을 해야할 것이다.

 

만약 위와같이 미리 예외를 명시적으로 던지도록 코딩해놨더라면 시간을 매우 아낄 수 있을 것이다.

(물론 이런경우 애초에 디비 칼럼을 non-nullable하게 설계하는 것이 좋다.)

 

 

IDE의 스마트 캐스팅을 적극 활용하는 것도 방법이다.

 

결론적으로, !! 연산자는 최대한 지양하는 것이 좋다.

  • non-nullable 객체는 처음부터 non-nullable 하게 만들자
  • nullable 객체나 필드는 꼭 null 처리를 하자
  • !! 연산자는 최대한 사용하지 말자
  • null 처리를 할 땐, 예외를 적절히 던지고 코멘트를 구체적으로 명시하자.

 

# 로그를 찍을 땐, 그 대상을 식별할 수 있는 값을 포함하자

유저의 식별값이 userId라고 했을 때, 예외나 로그를 찍어야 한다면 이를 꼭 포함해서 찍도록 한다.

 

값을 변경하는 경우는 해당 값을 로그에 포함하는 것도 좋은 습관이다.

 

logger.error("유저의 상태를 변경할 수 없습니다.") // bad

logger.error("[$userId] 유저의 상태($currentStatus -> $updateStatus)를 변경할 수 없습니다.") // good

 

물론 필요하지 않는 로그는 남기지 않도록 하자.

이는 나중에 cs나 이슈가 생겼을 때, 로그 분석을 어렵게 할 수도 있다.

 

로그를 level은 warn 보다는 error를 활용하자.

warn은 보통 무시해도 되는 오류라고 인식되는 경우가 많기 때문이다.

 

# 테스트 코드를 작성하자

테스트 코드는 기능 추가, 변경에 유연하게 대응할 수 있게 해 주고 리팩토링에 자신감을 갖게 해 준다.

 

그리고 버그를 사전에 잡아준다. (테스트 없이 믿음 배포를 하는 상남자/상여자 개발자는 없을 것이다...)

 

또한 잘 작성된 테스트 코드는 또 하나의 문서 역할을 할 수 있다.

 

단위 테스트 작성을 우선적으로 하고, 힘든 경우 통합 테스트나 e2e 테스트라도 작성하게끔 한다.

 

테스트를 자동화하는 것 자체가 굉장히 큰 의미가 있다. 이는 엔지니어링 오퍼레이션 비용을 대폭 낮춰준다.

 

특히나 복잡한 비즈니스 로직인 경우 빛을 발휘한다.

 

자세한 인사이트는 다음 글을 참조해도 좋다.

https://mopil.tistory.com/165

 

[Test] 어떤 테스트 코드를 작성하면 좋을까?

테스트코드는 "미래를 위한 투자"라고 생각한다. 당장 기능 구현에 꼭 필요한 부분은 아니지만, 추후 유지보수를 생각하면 테스트코드 작성은 필수적이라고 할 수 있다. 다만 대부분의 경우...

mopil.tistory.com

 

이는 비즈니스 로직이 주로 집약된 백엔드 한정일 수도 있다. 프론트 테스트 코드의 효용성은 아직도 체감하기 어려운 것 같긴 하다.

 

물론 화면 분기나 비즈니스 로직이 조금이라도 포함된 부분이면, 프론트 테스트코드도 의미를 갖는다고 동료 개발자가 말해주었다. (물론 서버보단 프론트가 테코를 잘 안짜긴 한다..)

 

# 구현체 말고 인터페이스에 의존하도록 하자

이는 단위 테스트를 작성하기 쉽도록 하기 위함 (Fake, in-memory 구현체 생성)이다.

 

인터페이스에 의존하면 상황에 따라 구현체를 갈아 끼울 수 있어, 훨씬 구조적으로 유연해진다.

 

자세한 인사이트는 아래글 참조

https://mopil.tistory.com/173

 

[Spring Boot] 서비스는 인터페이스를 꼭 구현해야할까? (w. 단위테스트)

간혹 서비스 객체를 인터페이스와 구현체로 분리해서 사용하는 레포들을 볼 수 있다. interface UserService { fun updateUserInfo(request: UpdateUserInfoRequest) fun getUserInfo(): UserInfo fun login() } @Service class UserServic

mopil.tistory.com

 

 

# SQL에 비즈니스 로직을 담는 것을 지양하자

레거시 코드를 보면 대부분 비즈니스 로직이 SQL에 집약되어 있는 걸 볼 수 있다.

 

이를 최대한 지양해야 한다. 이유는 다음과 같다.

 

  • 단위 테스트를 하기 힘들다
  • 컴파일 시점에 문법 오류를 찾을 수 없다
  • SQL이 길어질수록 (서브쿼리나 WITH) 가독성이 낮아진다
  • 쿼리가 느려진다 (데이터 가공 연산 등)

 

물론 성능상의 이슈로 어쩔 수 없는 경우도 존재한다. 그래서 최대한 지양하는 습관을 가지면 좋은 것 같다.

 

# 프론트, 다른 서버와 직접적으로 연결되는 부분은 설계를 매우 신중하게 하자

API 엔드포인트, requestBody, response 필드, query params 등을 말한다.

 

이는 변경사항이 생기면 이를 사용하는 프론트나 서버도 같이 대응해야 하고, 심한 경우 동시 배포를 해야 할 수도 있기 때문이다.

 

특히나 실험적인 API url을 만들어 두지 말자. 누군가 이를 사용하기 시작한다면 해당 url을 변경하는 것은 굉장히 큰일이 된다. (필요하다면 꼭 설명을 첨부하자)

 

request, response 응답 필드를 추가하는 건 문제 되지 않는다.

 

단, 기존 필드 명을 바꾸거나 삭제하는 경우 그 영향도를 항상 체크해야 한다.

 

따라서 해당 API에 불필요한 필드는 애초부터 만들지 않는 것이 좋다.

 

 

# 관리포인트를 최대한 줄여라

게시물 작성, 댓글 작성, 유저 프로필 변경 등 이미지와 관련된 모든 API에서 바로 멀티파트 파일을 올려 업로드한다고 가정해보자.

 

그리고 갑자기 S3 디렉토리 검증 로직을 모든 이미지 업로드에 적용해야 한다고 해보자.

 

이 경우 이미지 업로드 로직이 들어가 있는 모든 도메인에 해당 검증 로직을 추가해야한다.

 

이는 매우 비효율적이고 버그를 유발할 수 있다.

처음부터 이미지 업로드 모듈을 만들어 모든 역할을 위임하는게 좋았을 것이다.

 

이는 응집도를 높히고 결합도를 낮추는 방향으로 코딩하라는 말과 동일하다.


 

# 번외1) AI 기술을 적극 활용하자

필자는 개발할 때, 코파일럿과 챗GPT를 애용한다. 이 둘을 사용하면서 생산성이 말도 안 되게 향상되었음을 느꼈다.

 

물론 맹신하면 안 된다.

 

필자는 코파일럿이 자동완성 해준 코드를 무지성으로 라이브 배포했다가 장애를 낼 뻔한 적도 있다... 그래서 최소한의 검증할 수 있는 지식과 노력은 필요하다.

 

필자는 다음 두 상황에서 AI 기술을 적극 활용한다.

  • DTO 클래스를 만들 때 (코파일럿)
  • 변수, 함수명 추천받을 때 (챗GPT)
  • 코드를 리팩토링 시킬 때 (챗 GPT)

 

# 번외2) 코드리뷰를 적극적으로 받고 해 주자

코드리뷰를 해줄 동료가 없다면 안습이지만...ㅠ 위에서 논한 습관들이 잘 지켜졌는지 확인하는 방법은

 

해당 도메인과 관련 없는 동료에게 리뷰를 받는 것이다. 

 

그리고 반대로, 코드리뷰를 적극적으로 해주자. 필자 회사는 연차 상관없이 그 누구라도 어느 레포든 리뷰를 할 수 있는 문화가 자리 잡혀 있다.

 

리뷰를 통해서 왜 이렇게 작성했는지?를 많이 물어보는데, 그 과정에서 내가 모르던 지식과 인사이트를 많이 얻을 수 있었던 것 같다.

 

리뷰 해주는 동료를 위해 다음 두 가지를 지키면 좋은 것 같다. (실제로 요런 피드백을 받기도 했다.)

  • PR은 1000줄을 넘기지 않는다.
  • PR 코멘트를 적극 활용해서 내 코드를 설명하자. 리뷰어가 코드를 이해하는 시간을 줄이도록 노력하자.

 

 

# 번외3) 내가 알게 된 것을 적극적으로 공유하자

삽질해서 알게 된 것들을 정리하거나 동료들에게 공유하는 습관은 매우 좋은 것 같다.

 

이는 준 실시간 인수인계와도 동일하다고 생각한다.

 

그리고 혼자 성장하는 것보다, 팀원이 같이 성장하는 게 더 미래지향적인 것 같다.

 

특히나 문서로 정리를 해 두면, 미래의 내가 도움을 받을 수도 있다.

 

필자는 은행 특성상 매우 복잡한 비즈니스 로직을 많이 다루는데, 이를 매번 노션에 최신화를 하고 있다.

 

그리고 요건이 기억이 안 나면 자주 참조한다. 

 

 

# 번외4) 테스트를 개발보다 빡세게 하자

좋은 코드는 기본적으로 의도한 대로 제 기능을 해야한다. (즉, 버그가 없어야한다.)

 

개발은 재밌지만, 테스트는 귀찮고 재미없다.

 

그래서 테스트를 소홀히 하기 시작하게 된다. 그 결말은 항상 뭔가 오류가 난다.

 

아주 기초적인 버그를 디버깅 하지 못해서 장애를 유발하면, 이는 곧 내 자신에 대한 동료들의 신뢰 하락은 물론 심각하면 서비스 자체에 대한 고객의 대한 신뢰를 잃을 수도 있어서 굉장이 크리티컬하다.

 

대부분 개발은 금방한다. 테스트, QA에 더 많은 시간을 투자하는 습관을 가지자.

 

이는 테스트 코드를 작성해야하는 이유와도 연결된다. 

 

개발:테스트 노력 투자 비율을 4:6 정도로 의식적으로 잡도록 노력하면 좋을 것 같다.


혼자서 개발을 하다 보면 이러한 점들에 대해서 잊어버리곤 하는 것 같다. 그래서 "습관"을 들여놓는 것이 중요하다고 생각한다.

 

"귀찮은데 그냥 넘어가자~"라는 마인드로 코드를 쉽게 쉽게 작성하다 보면, 그것이 팀의 컨벤션이 되고 결국 기술 부채가 쌓여갈 것이다.

 

그리고 이러한 기술 부채는 청산하는데 매우 큰 비용(시간과 노력)을 지불해야 한다.

 

나중에 수정하지 말고 처음부터 제대로 만들자. 대부분 "나중"은 오지 않았다. 일은 항상 바쁘다.

반응형