빠르게 실패하기 vs 안전하게 실패하기
가능한 에러는 빠르게 처리하는 편이 좋다
과거 프로그래머들은 빠르게 실패하는데에서 생기는 잇점을 생각하지 못했기 때문에 안전하게 실패하는 쪽으로 프로그램을 구성했다.
안전하게 실패하는 시나리오에서는 사이드 이펙트가 나올수 있는 케이스가 많기때문에 클라이언트의 코드에 책임이 전이 될수도 있고, 실패 원자성1을 어기기도 쉽다.
책에서는 public 이거나 protected 로 정의된 메서드(=클라이언트와 계약을 가지는 메서드)는 실패되는 케이스를 자바독으로 남기라고 한다.
특정 어노테이션을 이용하여 매개변수가 nullable이거나 notnull이거나를 알리는 방법도 있다.
즉, 저자는 클라이언트에게 에러를 발생하는것 자체도 계약임을 어떤 방식으로든 알리라 라고 이야기한다.
그렇다면 실제로 빠르게 실패하는 방법은 어떤것이 있는가?
간단히 메소드 안에서 처리할수 있는 방법은 Objects 가 지원하는 정적 메소드들이 있다.
requireNonNull 시리즈와 isNull, nonNull, 그리고 checkIndex 시리즈가 있는데,
checkIndex 시리즈는 객체 자체를 검사하는게 아니라 객체의 range를 검사하는 것이라 생각보다 유용하지 않고,
파라미터 자체에서도 실수가 발생할수 있다.
requirNonNull 의 경우 null 체크만 가능하며, nullPointerException만 발생하기 때문에 사용하길 원한다면 try-catch를 통해 예외번역을 사용해야한다.
또, 예약어 assert를 소개하고있지만, 해당 방법은 vm에 -ea 플래그를 줘야하므로 유용하지 않을 수 있다.
나중에 사용하기 위한 매개변수는 더욱 신경써야한다.
예제 scratch_62.java 참고
전체 매개변수를 확인하기 위한 cost가 높을때나 실용적이지 않을때는 예외로 한다.
또, 계산 과정에서 암묵적으로 검사가 수행될때도 예외로 한다고 한다.
<회원과 정보의 관계 설명>
메소드는 최대한 범용적으로 설계해야 한다며 '매개변수에 제약을 두는게 좋다'가 아니라고 한다.
마땅히 객체지향적인 메소드는 범용적으로 설계되어야 한다. 하지만 필요할때는 제약을 두는게 맞다.
구현하려는 개념자체가 특정한 제약을 내재한 경우 = scratch_62 의 content 클래스. getter가 아님!
해당 아이템에서 벗어나지만 한층 더 올라가서 생각해보자.
validate는 언제 이루어져야 하는가? 라는 고민을 한번쯤 해본적이 있을것이다.
엉클 밥은 각자의 관심사를 지키기 위해서 레이어마다 적절한 validate을 수행해야한다고 했음.
나는 습관적으로 dto에서 간단한 정책적 validate를 수행했는데, 그러면 안된다는 이야기.
예를들면 전화번호 입력을 위한 010-1111-2222를 지키기 위해 정규식으로 dto에서 validate을 수행했다.
물론 효율을 생각한다면 이것이 맞을수 있으나, 레이어가 분리되어 책임을 가지는 아키텍쳐라면 엔티티가 그 비즈니스의 정책을 가져가는게 맞다고 본다.
역으로 생각해보면, 엔티티가 가진 정책이 바뀌면 dto에게 영향을 줄수있다는 뜻인데,
서로의 관심사가 다른데 하나의 이유로 두개의 레이어가 변경되는것은 부적절하다라는 결론이 나온다.(물론 현실은 그렇게 녹록치 않다)
https://groups.google.com/g/clean-code-discussion/c/latn4x6Zo7w/m/bFwtDI1XSA8J
https://ikenox.info/blog/where-to-put-validation-in-clean-architecture/
실제의 assert
책에서는 -ea 플래그를 주는 방법의 자바 예약어 assert를 알려줬지만,
플래그를 따로 줘야하는점에 있어서 프로덕트 코드에 사용하기 애매한 방법이다.
또, Objects 유틸리티에서 지원하는 방법은 유연성이 좋지 못하고, null 체크나 Array bound만 체크를 할수 있기 때문에 실무의 validate에는 적합하지가 못하다.
때문에 본인이 사용했던 방법을 따로 java파일로 함께 커밋함.
스프링 프레임워크 위에서만 동작한다.
https://eblo.tistory.com/63
에서 아이디어 얻음
클린한 코드를 작성하기 위해서는 빠르게 실패하기를 따르는것이 좋다.
빠르게 실패할때 생기는 잇점 : 유지보수 편의성, 높은 결합도 문제 해소
버그, 입출력 문제, 메모리 오버플로등이 발생한 상황에서도 소프트웨어가 계속해서 실행될수 있도록 해야한다는 철학
null을 반환하는 방법도 일종의 생존기법
예를들어 메소드가 처리해야하는 리소스가 비어있다는것은 클라이언트의 잘못이지만,
예외를 던지거나 프로그램이 죽는것보다는 null을 반환하거나 -1등을 리턴하여 클라이언트가 이를 처리할수있도록 함
문제가 발생하면 곧바로 실행을 중단하고 예외를 던짐
결과에 대해서는 클라이언트가 지불해야하는 비용이라 치부함.
모든 케이스에 대해 실패 지점이 정해져있기 때문에 테스트가 쉽고, 문서화 하기 쉽고, 수정하기 쉽다.
실패를 감추기보다 눈에띄게 만들어 클라이언트에게 주의를 시킨다.
저자는 자바독을 통해 발생할수 있는 예외를 남기라고 했지만,
한층 더 생각해보면 그냥 메소드 시그니처에 예외를 남기는게 낫지 않나?(혹은 체크 예외를 사용하든지) 생각했다.
클린코드에서는 반대로 언체크예외를 사용할것을 권장하더라.
https://cleancodes.tistory.com/5
ocp를 깨고, 캡슐화를 방해하기 때문이라고 함.
[1] : 완전한 객체이거나, 실패하거나. 불완전한 객체를 만들지 않는 성질.
참고
https://github.com/SeolYoungKim/effective_java_study/tree/main