이펙티브 코틀린(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 를 가질 수 없다
◦
val 을 var 로 오버라이드 할 수 있다
가별 컬렉션과 읽기 전용 컬렉션 구분하기
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 는 처리할 때 필요한 정보를 가질 수 있다
•
충분이 예측할 수 잇는 범위의 오류는 null 과 Failure 를 사용하는것이 좋음
예시 코드
•
null 을 처리해야 한다면, 개발자가 안전하게 처리하거나 Elvis 연산자 같은 null-safety 기능을 활용할 수 있음
예시 코드
•
Result 와 같은 Union Type 을 반환하면 when 표현식을 사용해서 처리할 수 있음
예시 코드
정리
•
개발자는 항상 자신이 안전하게 결과를 받을 수 있을거라 생각하기 때문에 nullable 을 반환하면 안된다
•
개발자에게 null 이 발생할 수 있다는 경고를 주려면 getOrNull 등을 사용해서 예측할 수 있게 해주는것이 좋다