토비의 스프링 책을 읽으며 꼭 기억하고 싶은 내용을 기록했습니다.
출처
이일민. (2012). 토비의 스프링. 서울:에이콘출판.
4장. 예외
예외 처리 핵심 원칙
모든 예외는 적절하게 복구되든지 아니면 작업을 중단시키고 운영자 또는 개발자에게 분명하게 통보돼야 한다.
체크 예외
자바가 throws를 통해 던질 수 있는 예외
- Error
- Exception과 체크 예외
- RuntimeException과 언체크/런타임 예외
예외 전환
예외를 메소드 밖으로 던지는 것. 예외 회피와 달리, 발생한 예외를 그대로 넘기는게 아니라 적절한 전환해서 던짐.
예외처리 전략
예전에는 복구할 가능성이 조금이라도 있다면 체크 예외로 만든다고 생각했는데, 지금은 항상 복구할 수 있는 예외가 아니라면 일단 언체크 예외로 만드는 경향이 있다.
애플리케이션 예외
애플리케이션 자체의 로직에 의해 의도적으로 발생시키고, 반드시 catch해서 무엇인가 조치를 취하도록 요구하는 예외
우선 예외상황에 대한 리턴 값을 명확하게 코드화하고 잘 관리하지 않으면 혼란이 생길 수 있다. 정상적인 처리가 안 됐을 때 전달하는 값의 표준 같은 것은 없다. 어떤 개발자는 0을 생각할 수도 있고, -1이나 -999를 돌려주는 개발자도 있다. 일관된 예외상황에서의 결과 값에 대한 정책이 완벽하게 갖춰져 있고, 사전에 상수로 정의해둔 표준 코드를 사용하지 않는다면 자칫 개발자 사이의 의사소통 문제로 인해 제대로 동작하지 않을 위험이 있다. 또 한 가지 문제는 결과 값을 확인하는 조건문이 자주 등장한다는 점이다. 이런 식으로 결과를 돌려주는 메소드를 연이어 사용하는 경우라면 if 블록이 범벅된 코드가 이어질지 모른다. 코드는 지저분해지고 흐름을 파악하고 이해하기가 힘들어질 것이다.
두 번째 방법은 정상적인 흐름을 따르는 코드는 그대로 두고, 잔고 부족과 같은 예외 상황에서는 비즈니스적인 의미를 띈 예외를 던지도록 만드는 것이다. 잔고 부족인 경우라면 InsufficientbalanceException 등을 던진다. 예외상황을 처리하는 catch 블록을 메소드 호출 직후에 둘 필요는 없다. 정상적인 흐름을 따르지만 예외가 발생할 수 있는 코드를 try 블록 안에 깔끔하게 정리해두고 예외상황에 대한 처리는 catch 블록에 모아둘 수 있기 때문에 코드를 이해하기도 편하다. 번거로운 if문을 남발하지 않아도 된다.
이 때 사용하는 예외는 의도적으로 체크 예외로 만든다. 그래서 개발자가 잊지 않고 잔고 부족처럼 자주 발생 가능한 예외상황에 대한 로직을 구현하도록 강제해주는게 좋다. 무책임하게 throws Exception을 습관적으로 달아놓은 경우라면 이마저도 놓칠 가능성이 있긴 하지만, 기본적으로 런타임 예외로 만들어두는 것보다는 상대적으로 안전하다.
5장 서비스 추상화
테스트
- 테스트에 사용할 데이터를 경계가 되는 값의 전후로 선택하는 것이 좋다.
- 레벨이 미리 정해진 경우와 레벨이 비어있는 두 가지 경우에 각각 add() 메소드를 호출하고 결과를 확인하도록..
코드 개선
- 코드에 중복된 부분은 없는가?
- 코드가 무엇을 하는 것인지 이해하기 불편하지 않은가?
- 코드가 자신이 있어야 할 자리에 있는가?
- 앞으로 변경이 일어난다면 어떤 것이 있을 수 있고, 그 변화에 쉽게 대응할 수 있게 작성되어 있는가?
객체지향 프로그래밍
객체지향적인 코드는 다른 오브젝트의 데이터를 가져와서 작업하는 대신 데이터를 갖고 있는 다른 오브젝트에게 작업을 해달라고 요청한다. 오브젝트에게 데이터를 요구하지 말고 작업을 요청하라는 것이 객체지향 프로그래밍의 가장 기본이 되는 원리이기도 하다.
로직과 환경을 분리해야 하는 이유
기술적인 수정사항도 마찬가지다. 애플리케이션 계층의 코드가 특정 기술에 종속돼서 기술이 바뀔 때마다 코드의 수정이 필요하다면 어떨지 상상해보자. 그나마 트랜잭션 동기화 기법을 사용해 서비스 클래스의 코드를 간략히 했다고 하더라도, JDBC에서 JTA로 트랜잭션 기술이 변경되면, 아마 그에따라 (서비스 클래스 수) * (트랜잭션을 사용하는 메소드 수)만큼의 엄청난 코드를 수정해야 할 것이다. 그런 경우와 XML 설정을 몇 줄 수정하는 것으로 트랜잭션 기술을 한 번에 전환하는 방식을 비교해보자. 그 차이는 상상도 할 수 없을 만큼 크다. 단지 수정하는 작업량만의 문제가 아니다. 많은 코드를 수정하는 작업에선 그만큼 실수가 일어날 확률이 높다. 치명적인 버그가 도입될 가능성도 있다. 개발 중이 아니라 운영 중인 코드에 이런 수정이 필요하다면, 아마도 엄청난 부담을 안고 시작하거나 아니면 겁나서 도저히 못 하겠다고 저항해야 할지도 모르겠다.
그래서 적절하게 책임과 관심이 다른 코드를 분리하고, 서로 영향이 없도록 다양한 추상화 기법을 도입하고, 애플리케이션 로직과 기술/환경을 분리하는 등의 작업은 갈수록 복잡해지는 엔터프라이즈 애플리케이션에는 반드시 필요하다. 이를 위한 핵심적인 도구가 바로 스프링이 제공하는 DI다. 스프링의 DI가 없었다면 인터페이스를 도입해서 나름 추상화를 했더라도 적지 않은 코드 사이의 결합이 남아 있게 된다. PlatformTransactionManager 인터페이스가 제공하는 트랜잭션 메소드를 적용했지만 리스트 5-45에서처럼 new DataSourceTransactionManager()라는 구체적인 의존 클래스 정보가 드러나는 코드가 존재할 때를 생각해보자. 이런 식이라면 인터페이스로 추상화를 안 했을 때보다는 훨씬 적긴 하겠지만 로우레벨 기술의 변화가 있을 때마다 비즈니스 로직을 담은 코드의 수정이 발생한다. 결국 DI를 통해 PlatformTransactionManager의 생성과 의존관계 설정을 스프링에 맡긴 덕에 완벽하게 트랜잭션 기술에서 자유로운 UserService를 가질 수 있게 된 것이다.
스프링과 DI의 장점
스프링의 의존관계 주입 기술인 DI는 모든 스프링 기술의 기반이 되는 핵심 엔진이자 원리이며, 스프링이 지지하고 지원하는, 좋은 설계와 코드를 만드는 모든 과정에서 사용되는 가장 중요한 도구다. 스프링을 DI 프레임워크라고 부르는 이유는 외부 설정정보를 통한 런타임 오브젝트 DI라는 단순한 기능을 제공하기 때문이 아니다. 오히려 스프링이 DI에 담긴 원칙과 이를 응용하는 프로그래밍 모델을 자바 엔터프라이즈 기술의 많은 문제를 해결하는 데 적극적으로 사용하고 있기 때문이다. 또, 스프링과 마찬가지로 스프링을 사용하는 개발자가 만드는 애플리케이션 코드 또한 이런 DI를 활용해서 깔끔하고 유연한 코드와 설계를 만들어낼 수 있도록 지원하고 지지해주기 때문이다.
