Clean Code With Kotlin 2

전편 : https://velog.io/@woga1999/Clean-Code-With-Kotlin-1

에러 핸들링

예상하지 못한 시나리오에 대처하는 법에 대해 이야기 할 때는 다음 사항을 고려해야 한다.

  • 사용자 정의 오류보다는 표준 오류를 선호하자.
  • 에러 처리도 중요하지만 논리를 흐리게 하는 것은 잘못된 것이다.
  • 코틀린에는 unchecked exception들만 있다.
  • 에러 결과가 부족할수 있는 경우 null 또는 failure 결과를 선호하자.

또한, 코틀린에서는 함수가 아무것도 반환하지 않을 때 Nothing을 사용할 수 있다.

fun infiniteLoop(): Nothing {
    while (true) {
        println("Hi there!")
    }
}
fun throwException(): Nothing {
    throw IllegalStateException()
}

에러 핸들링 또는 상태 관리할 때 쓰기 좋은 코틀린 속 개념이 하나 더 있는데, 그건 바로

sealed class

공식 문서에 따르면

"Sealed 클래스는 제한된 클래스 계층을 나타내기 위해 사용됩니다. 값은 제한된 집합의 유형 중 하나를 가질 수 있지만 다른 유형을 가질 수 없습니다. 어떤 의미에서는 열거형 값의 집합도 제한되지만 각 열거형 상수는 단일 인스턴스로만 존재하는 반면 밀폐형 클래스의 하위 클래스는 상태를 포함할 수 있는 여러 인스턴스를 가질 수 있습니다."

  • result class를 반활 할 때
sealed class MovieSearchResult
data class MovieFound(val movie: Movie) : MovieSearchResult()
object MovieNotFound : MovieSearchResult()
object DatabaseOffline : MovieSearchResult()

fun displayMovieResult(movieResult: MovieSearchResult) {
    when(movieResult) {
        is MovieFound -> println("yey, we found the movie")
        is MovieNotFound -> TODO()
    }
}
val <T> T.exhaustive: T
    get() = this
    
fun displayMovieResult(movieResult: MovieSearchResult) {
    when(movieResult) {
        is MovieFound -> println("yey, we found the movie")
        is MovieNotFound -> TODO()
    }.exhaustive
}

테스트

테스트를 클린하게 유지하자:

  • 테스트 코드에 프로덕션 코드와 동일한 Same Quality를 사용하므로 가독성이 중요하다
  • 테스트 코드는 유연성, 유지보수성, 재사용 가능성, 가독성을 유지하고 강화해야한다(enhanced)
  • 테스트 코드에서도 읽기 쉬운 동일한 도메인 언어를 사용해야 한다.
  • 우리가 프로덕션 코드에서 하는 것처럼 테스트 코드를 리팩터링해야 한다.

테스트 당 하나의 aseert을 포함해야 한다: 테스트는 1개의 fail 원인을 갖기 때문이다.

  • Every test function in a JUnit test should have one and only one assert statement.
  • Given, When, Then naming (BDD – Behavior Driven Development 행동 주도 개발)
@Test
    public void givenValidInputsWhenGetRatesCalledThenLiveDataObserverEmitsSuccessState() {
        //given
        String date = "2000-10-10";
        String baseCurrency = "USD";
        Single<ExchangeRateResponse> expectedResponse = Single.just(ExchangeRateResponse.EMPTY);

        //when
        when(mockRepository.requestExchangeRates(date, baseCurrency)).thenReturn(expectedResponse);
        testSubject.getRates(date, baseCurrency);

        //then
        State result = testSubject.getExchangeRateData().getValue();
        assertThat(result).isEqualTo(new State.Success(ExchangeRateResponse.EMPTY));

        verify(mockObserver).onChanged(isA(State.Loading.class));
        verify(mockObserver).onChanged(isA(State.Success.class));

        verify(mockObserver, times(1)).onChanged(isA(State.Loading.class));
        verify(mockObserver, times(1)).onChanged(isA(State.Success.class));

        verify(mockObserver, never()).onChanged(isA(State.Failure.class));

        verifyNoMoreInteractions(mockObserver);
    }
  • The best thing to do is to minimize the number of asserts.
  • Test just one concept per test function.

FIRST 원칙

테스트 코드를 작성할 때는 이 원칙을 준수하자.

  • Fast : 테스트는 고속/신속하게 실행되어야 함
  • Independent : 테스트는 서로 의존하지 않아야 한다. 원하는 순서로 실행할 수 있어야 한다.
  • Repetable : 특정 구조를 필요로 하지 않고 모든 환경에서 반복 가능해야 한다.
  • Self-validating : Boolean 출력(true/false)이 있어야 한다.
  • Timely : 운영 코드를 작성하기 직전에 적시에 작정해야 한다.

주석

잘못된 코드에 대한 주석은 추가하지 말고 본인이 코드로 설명이 될 때까지 다시 작성하자.

example

  • no!! comment!! like this!!
class Log {
	/** The data. */
    var data: String = ""
    
    /** The number of data. */
    var numberOfData = 0
    
    /**
    * Secondary constructor.
    */
    constructor(data: String){
    }
}
fun updataUserPoints(user: User, points: Int) {
	try {
    	user.points += points
    } //try
    catch (e: java.lang.Exception) {
    	//User not found
        println("user not found")
    } //catch
 }
  • need comments

<Legal Comments>

/**
Coptyright (c) <year> <copyrigth holders>

Permission is hereby granted, blah blah
*/

<Warning of consequences>

fun makeStandardDateFormant(): SimpleDateFormat? {
	//SimpleDateFormat is not thread safe,
    //so we need to create each instance independently.
    val dateFormat = SimpleDateFormat("EEE, dd MMM yyyy HH:mm: ss z")
    dateFormat.timeZone = TimeZone.getTimeZone("GMT")
    return dateFormat

+)

  • bad
// Check to see if the employee is eligible for full benefits
if (employee.rate.equalsIgnoreCase("hours") &&
employee.objectiveDone > 3)
  • good
if (employee.isEligibleForFullBenefits())

Reference

https://magdamiu.com/2021/08/23/clean-code-with-kotlin-2/

좋은 웹페이지 즐겨찾기