Blog

이펙티브 코틀린(1)

이펙티브 코틀린(1)

코틀린은 다양한 설계 지원을 통해 애플리케이션의 잠재적인 오류를 줄여준다. → 프로그래밍에서 안정성은 매우 중요한 요소이다.
코틀린은 처음부터 대규모 애플리케이션을 만들기 위한 프로그래밍 언어로 설계되었다
코틀린은 굉장히 오랜 시간 동안 만들어지고 있었다
2010년에 처음 개발되었지만, 2016년 2월에 Stable Version 이 공식 배포되었다
2010년 대비 2016년 코틀린은 완전히 다른 모습으로 보인다
6년동에 가까운 시간 동안 베타 버전으로 유지되었기 때문에 계속해서 변화할 수 있었다
코틀린은 학문 목적 또는 취미 목적의 언어와 다르게 새로운 개념을 실험하기 위한 부분이 없다
Property Delegation 과 같은 새로운 개념들을 도입하기는 했지만, 대부분 기존 프로그래밍 언어에 있던 개념들의 장단점을 분석하고 개선한 뒤 도입했다
코틀린은 DEPRECATED 되거나 수정될 내용에 대해서 IDE 의 지원으로 코드를 마이그레이션할 수 있게 도와준다
코틀린은 모듈 단위로 프로그램을 설계한다
Class, Object, Funciton, Type Alias, Top-Level Property 등 다양한 요소로 구성되어 있다

코틀린의 철학

핵심 철학: 실용주의 ( Pragmatism )
생산성 ( Productivity ): 애플리케이션을 빠르게 생산한다
확장성 ( Scalability ): 애플리케이션의 규모가 커져도, 개발 비용이 증가하지 않는다
유지보수성 ( Maintainability ): 유지보수를 쉽게 할 수 있다
신뢰성 ( Reliability ): 애플리케이션이 예상한 대로 동작하기 때문에 오류가 적다
효율성 ( Efficiency ): 애플리케이션이 굉장히 빠르게 동작하며, 리소스( CPU, Memory 등 )가 더 적게 필요하다

1-1. 가변성을 제한하라

상태를 갖게 된다는것은 양날의 검이다
변하는 요소를 표현할 수 있다는 점은 장점이지만, 적절하게 관리하는것이 생각보다 어렵다
프로그램을 이해하고 디버그하기 어렵다
가변성이 있으면 코드의 실행을 추론하기 어렵다
멀티쓰레드 프로그램에서는 적절한 동기화가 필요하다
테스트하기 어렵다
상태 변경이 일어날 때, 변경을 다른 부분에 알려야 하는 경우가 발생한다

Read Only Property ( val )

val 을 이용해서 읽기 전용 프로퍼티를 만들 수 있다 ( 읽고 쓸 수 있는 프로퍼티는 var 로 만든다 )
주의) 읽기 전용 프로퍼티가 mutable 객체를 담고 있다면 내부적으로 변할 수 있다 ( val 이 immutable 을 의미하는것은 아니다 )
코틀린의 프로퍼티는 기본적으로 캡슐화되어 있고, 사용자 정의 접근자 ( getter, setter ) 를 가질 수 있다
var 는 setter, getter 를 모두 가질 수 있지만 val 은 setter 를 가질 수 없다
valvar 로 오버라이드 할 수 있다

가별 컬렉션과 읽기 전용 컬렉션 구분하기

Iterable, Collection, Set, List 인터페이스는 읽기 전용이고, 앞에 Mutable 이 붙은 인터페이스는 변경이 가능한 메소드를 추가했다.
읽기 전용 컬렉션이 내부의 값을 변경할 수 없다는 의미는 아니다.
Iterable<T>.map, Iterable<T>.filter 는 ArrayList 를 반환한다.
ArrayList 는 변경할 수 있는 리스트이다.
예시 코드) 컬렉션 다운캐스팅

데이터 클래스의 Copy

Immutable 객체 사용 장점
한 번 정의된 상태가 유지되기 때문에 코드를 이해하기 쉽다
immutable 객체는 공유했을 때도 충돌이 발생하지 않기 때문에 병렬 처리를 안전하게 할 수 있다
immutable 객체에 대한 참조는 변경되지 않으므로 캐시하기 쉽다
immutable 객체는 방어적 복사본 (defensie copy) 를 만들 필요가 없다
Deep Copy 를 하지 않아도 된다
다른 객체를 만들 때 활용하기 좋다
set 또는 map 의 키로 사용할 수 있다
( Recommendation ) data class 를 사용하면 copy 메서드를 사용할 수 있다
data class User( val name: String, val surname: String ) var user = User("claude", "seo") user = user.copy(surname="ko") print(user)
Kotlin

변경 가능 지점 노출하지 말기

상태를 나타내는 mutable 객체를 외부에 노출하면 내부의 값이 변경될 수 있기 때문에 매우 위험하다
예시 코드

정리

mutable 보다는 immutable 로 작성하는것이 좋다

1-2. 변수의 스코프를 최소화하라

mutable property 는 좁은 스코프에 걸쳐 있을수록 그 변경을 추적하는것이 쉽다
변수는 정의할 때 초기화되는것이 좋다
if, when, try-catch, Elvis 표현식 등을 활용하면 최대한 변수를 정의할 때 초기화할 수 있다
Scope 를 잘못 설정할 경우 캡처링 문제가 발생할 수 있다
예시 코드

정리

변수의 스코프는 최대한 좁게 만들어서 활용하는것이 좋다
lambda 에서 변수를 캡처한다

1-3. 최대한 플랫폼 타입을 사용하지 말라

플랫폼 타입 ( Platform Type ): 다른 프로그래밍언어에서 전달되어서 nullable 인지 아닌지 알 수 없든 타입을 의미한다.
플랫폼 타입은 타입 이름 뒤에 ! 기호를 붙여서 표기한다.
예시 코드

정리

플랫폼 타입은 활용하는곳까지 영향을 줄 수 있기 때문에 지양하는것이 좋다
자바 코드를 직접 조작할 수 있다면 가능한 어노테이션을 붙여서 사용하는게 좋다 ( @Nullable, @NotNull )

1-4. inferred 타입으로 리턴하지 말라

할당 때 inferred type 은 오른쪽에 있는 피연산자에 맞게 설정된다.
절대로 Super Class 또는 Interface 로 설정되지 않는다.
예시 코드

정리

타입을 확실하게 지정해야 하는 경우에는 명시적으로 타입을 지정해야 한다
외부 API 를 만들 때는 반드시 타입을 지정하고, 특별한 이유와 확실한 확인 없이 제거하지 않는다.

1-5. 예외를 활용해 코드에 제한을 걸어라

동작에 제한을 걸 때 사용하는 방법
require: argument 를 제한할 수 있다
check: 상태와관련된 동작을 제한할 수 있다
assert: 어떤 것이 true 인지 확인할 수 있다. ( 테스트 모드에서만 작동 )
return 또는 throw 와 함께 활용하는 Elvis 연산자
예시 코드
require 블록과 check 블록이 나온 이후면은 해당 조건은 이후로도 true 일 거라 가정한다
예시 코드
nullability 를 목적으로 오른쪽에 throw 혹은 return 구문을 사용할 수 있다
예시 코드

정리

제한을 훨씬 더 쉽게 확인할 수 있다
애플리케이션을 더 안정적으로 지킬 수 있다
스마트 캐스팅을 활용할 수 있다

1-6. 사용자 정의 오류보다는 표준 오류를 사용하라

표준 라이브러리의 오류는 많은 개발자들이 알고 있으므로 재사용하는것이 좋다.
일반적으로 사용되는 예외
IllegalArgumentExeception, IllegalStateException: require, check 를 사용해 throw 할 때 사용하는 예외
IndexOutOfBoundsException: 인덱스 파라미터의 값이 범위를 초과한 경우 사용하는 예외
ConcurrentModificationException: 동시 수정(concurrent modification) 을 금지했는데 발생한 경우 사용하는 예외
UnsupportedOperationException: 사용자가 사용하려고 했던 메서드가 현재 객체에서 사용할 수 없는 경우 사용하는 예외
NoSuchElementException: 사용자가 사용하려고 했던 요소가 존재하지 않을 때 사용하는 예외

1-7. 결과 부족이 발생할 경우 null 과 Failure 를 사용하라

함수가 의도한대로 동작하지 않는 케이스가 발생할 수 있음
e.g) 네트워크 문제로 서버에서 데이터를 못가져오는 경우
e.g) 데이터 전문의 인터페이스가 다른 경우
e.g) 조건에 맞는 데이터가 없는 경우
일반적으로 처리하는 방식은 크게 2가지 있음
1.
null 또는 sealed class 를 반환
2.
예외를 throw

예외를 throw 하는 방식

예외는 정보를 전달하는 방법으로 사용해서는 안되며, 예외적인 상황이 발생했을 때 사용하는것이 좋음
이유
 많은 개발자가 예외가 전파되는 과정을 제대로 추적하지 못함
코틀린의 모든 예외는 unchecked 예외 처리임
checked 예외: 사용자가 반드시 처리하게 강제되는 예외
unchecked 예외: 처리하지 않아도 실행에 문제가 없는 예외
명시적인 테스트만큼 빠르게 동작하지 않음
try-catch 블록안에 코드를 배치하면, 컴파일러가 할 수 있는 최적화가 제한됨
예측하기 어려운 예외적인 범위의 오류일 경우 예외를 throw 해서 처리하는것이 좋음

null 또는 selaed class 를 반환

추가적인 정보를 전달해야 한다면 sealed result 를 사용하고, 그렇지 않으면 null 을 사용하는것이 일반적이다 → Failure 는 처리할 때 필요한 정보를 가질 수 있다
충분이 예측할 수 잇는 범위의 오류는 nullFailure 를 사용하는것이 좋음
예시 코드
null 을 처리해야 한다면, 개발자가 안전하게 처리하거나 Elvis 연산자 같은 null-safety 기능을 활용할 수 있음
예시 코드
Result 와 같은 Union Type 을 반환하면 when 표현식을 사용해서 처리할 수 있음
예시 코드

정리

개발자는 항상 자신이 안전하게 결과를 받을 수 있을거라 생각하기 때문에 nullable 을 반환하면 안된다
개발자에게 null 이 발생할 수 있다는 경고를 주려면 getOrNull 등을 사용해서 예측할 수 있게 해주는것이 좋다