22깨끗한 코드와 오류 처리는 확실히 연관성이 있다. 오류 처리는 중요하지만 오류 처리 코드로 인해 프로그램 논리를 이해하기 어려워 진다면 깨끗한 코드라 부르기 어렵다.
33
44## 1. 오류 코드보다 예외를 사용하라
5-
5+ 오류코드를 사용할시 코드가 복잡해진다. 함수를 호출한 즉시 오류를 확인해야 하기 때문이다.
6+ 예외처리를 할시 코드도 더 깔끔해지고 논리가 오류처리 코드와 뒤섞이지 않게 된다.
67
78### 1-1. 오류코드 sample
89```
@@ -42,10 +43,11 @@ public class DeviceController {
4243 logger.log(e);
4344 }
4445 }
45-
46+
4647 private void tryToShutDown() throws DeviceShutDownError {
4748 DeviceHandle handle = getHandle(DEV1);
4849 DeviceRecord record = retrieveDeviceRecord(handle);
50+
4951 pauseDevice(handle);
5052 clearDeviceWorkQueue(handle);
5153 closeDevice(handle);
@@ -62,15 +64,191 @@ public class DeviceController {
6264```
6365
6466## 2. Try-Catch-Finally 문부터 작성하라
67+ try 블록에 무슨일이 생기든지 catch 블록은 프로그램 상태를 일관성 있게 유지해야 한다.
68+ 그러므로 예외가 발생할 코드를 짤때는 try-catch-finally 문으로 시작하는 편이 낫다.
69+ 그러면 try블록에서 무슨 일이 생기든지 호출자가 기대하는 상태를 정의하기 쉬워진다.
70+ 먼저 강제로 예외를 일으키는 테스트 케이스를 작성한 후 테스트를 통과하게 코드를 작성하는 방법을 권장한다.
71+ 그러면 자연스럽게 try블록의 트랜잭션 범위부터 구현하게 되므로 범위 내 트랜잭션 본질을 유지하기 쉬워진다.
72+ ```
73+ //파일이 없으면 예외를 던지는지 알아보는 단위 테스트
74+ @Test(expected = StorageException.class)
75+ public void retrieveSectionShouldThrowOnInvalidFileName() {
76+ sectionStore.retrieveSection("invalid - file");
77+ }
78+ //try catch로 예외를 던지므로 테스트가 성공한다
79+ public List<RecordedGrip> retrieveSection(String sectionName) {
80+ try {
81+ FileInputStream stream = new FileInputStream(sectionName)
82+ } catch (Exception e) {
83+ throw new StorageException("retrieval error", e);
84+ }
85+ return new ArrayList<RecordedGrip>();
86+ }
87+
88+ ```
89+ 그런뒤 예외 유형을 좁혀 실제 예외를 찾아내면서 리펙토링한다.
90+ ```
91+ public List<RecordedGrip> retrieveSection(String sectionName) {
92+ try {
93+ FileInputStream stream = new FileInputStream(sectionName);
94+ stream.close();
95+ } catch (FileNotFoundException e) {
96+ throw new StorageException("retrieval error", e);
97+ }
98+ return new ArrayList<RecordedGrip>();
99+ }
100+
101+ ```
65102
66103## 3. 미확인(unchecked) 예외를 사용하라
104+ 확인된 예외는 OCP(Open Closed Principle, 개방폐쇄원칙 - '소프트웨어 개체는 확장에 대해 열려 있어야 하고, 수정에 대해서는 닫혀 있어야 한다'는 프로그래밍 원칙)를 위반한다.
105+ 하위 단계에서 코드를 변경하면 상위 단계 메서드 선언부를 전부 고쳐야 한다.
106+ throw 경로에 위치하는 모든 함수가 최하위 함수에서 던지는 예외를 알아야 하므로 캡슐화가 깨진다.
107+
108+ | | Checked Exception | UnChecked Exception |
109+ | ---| :---:| ---:|
110+ | 확인 시점 | 컴파일 시점 | 런타임 시점 |
111+ | 처리 여부 | 반드시 처리 | 명시적으로 처리하지 않아도 됨 |
112+ | 트랜잭션 처리 | roll-back 하지 않음 | roll-back 함 |
113+ | 예시 | IOException, ClassNotFoundException | NullPointerException, ArithmeticException |
67114
68115## 4. 예외에 의미를 제공하라
116+ 예외에 정보를 충분히 담아서 던지면, 오류가 발생한 원인과 위치를 찾기 쉬워진다.
69117
70118## 5. 호출자를 고려해 예외 클래스를 정의하라
119+ 오류를 분류할 수 있는 방법은 많다.
120+ - 발생한 위치
121+ - 발생한 컴포넌트
122+ - 유형 : 네트워크 실패, 디바이스 실패, 프로그래밍 오류 등
123+
124+ 하지만 프로그래머에게 가장 중요한 관심사는 오류를 잡아내는 방법이 되어야 한다.
125+ 외부 라이브러를 그대로 사용한 경우에는 외부 라이브러리가 던질 예외를 모두 잡아야 한다.
126+
127+ ```
128+ ACMEPort port = new ACMEPort(12);
129+
130+ try {
131+ port.open();
132+ } catch (DeviceResponseException e) {
133+ reportPortError(e);
134+ logger.log("Device response exception", e);
135+ } catch (ATM1212UnlockedException e) {
136+ reportPortError(e);
137+ logger.log("Unlock exception", e);
138+ } catch (GMXError e) {
139+ reportPortError(e);
140+ logger.log("Device response exception");
141+ } finally {
142+ ...
143+ }
144+ ```
145+ 대다수 상황에서 오류를 처리하는 방식은 오류 종류와 무관하게 비교적 일정하다
146+ 1 . log를 남긴다
147+ 2 . 계속 수행가능한지 확인한다
148+ 위 경우에도 예외 유형과 무관하게 모두 동일했다. 이 경우 외부 라이브러리를 호출하는 API를 감싸면서 예외 유형을 하나만 던지게 수정해보자.
149+
150+
151+ ```
152+ LocalPort port = new LocalPort(12);
153+
154+ try {
155+ port.open();
156+ } catch (PortDeviceFailure e) {
157+ reportError(e);
158+ logger.log(e.getMessage(), e);
159+ } finally {
160+ ...
161+ }
162+
163+ public class LocalPort {
164+ private ACMEPort innerPort;
165+
166+ public LocalPort(int portNumber) {
167+ innerPort = new ACMEPort(portNumber);
168+ }
169+
170+ public void open() {
171+ try {
172+ innerPort.open();
173+ } catch (DeviceResponseException e) {
174+ throw new PortDeviceFailure(e);
175+ } catch (ATM1212UnlockedException e) {
176+ throw new PortDeviceFailure(e);
177+ } catch (GMXError e) {
178+ throw new PortDeviceFailure(e);
179+ }
180+ }
181+ }
182+ ```
183+ 외부 API를 감싸는 클래스는 매우 유용하다. 외부 API와 프로그램 사이에 의존성이 크게 줄어든다.
71184
72185## 6. 정상 흐름을 정의하라
186+ catch문에서 예외를 처리하는 경우 코드가 지저분해지는 일이 발생할 수 있다.
187+
188+ ```
189+ try {
190+ MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
191+ //식비를 비용으로 청구시 총계에 더한다
192+ m_total += expenses.getTotal();
193+ } catch(MealExpensesNotFound e) {
194+ //그게 아니면 일일 기본 식비를 총계에 더한다
195+ m_total += getMealPerDiem();
196+ }
197+ ```
198+ 식비를 비용으로 청구했다면 그걸 더하고, 아니면 일일 기본 식비를 더하는 코드이다.
199+ 만약 청구식비가 없으면 일일 기본 식비를 반환하도록 DAO를 수정하면 아래와 같이 간결하게 된다.
200+ ```
201+ public class PerDiemMealExpenses implements MealExpenses {
202+ public int getTotal() {
203+ // 기본값으로 일일 기본 식비를 반환한다.
204+ }
205+ }
206+ ```
207+ ```
208+ MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
209+ m_total += expenses.getTotal();
210+ ```
211+ 이와같은 경우를 특수사례패턴(Special Case Pattern) 이라 한다.
212+ 클래스를 만들거나 객체를 조작해 특수 사례를 처리하는 방식이다.
73213
74214## 7. null을 반환하지마라
215+ null을 반환하면 호출자가 null을 확인해야 한다. null확인 코드로 가득한 화면을 계속봐야 한다.
216+ 이건 호출자에게 문제를 떠넘기는 행위이다.
217+ null 대신 예외를 던지거나 특수 사례 객체(ex. Collections.emptyList())를 반환하라.
218+ ```
219+ List<Employee> employees = getEmployees();
220+
221+ if (employees != null) {
222+ for(Employee e : employees) {
223+ totalPay += e.getPay();
224+ }
225+ }
226+ ```
227+ 위 예의 경우 null이 아닌 빈 리스트를 반환한다면 더 깨끗해진다.
228+ ```
229+ List<Employee> employees = getEmployees();
230+
231+ for(Employee e : employees) {
232+ totalPay += e.getPay();
233+ }
234+
235+ public List<Employee> getEmployees() {
236+ if (..직원이 없다면..)
237+ return Collections.emptyList();
238+ }
239+ ```
75240
76241## 8. null을 전달하지 마라
242+ 인수로 null을 전달하면 함수 내에서 예외를 던지거나 assert 문을 사용할 수는 있다.
243+ 하지만 NullPointException 문제를 해결해 줄 수는 없다.
244+ 애초에 null을 넘기지 못하도록 금지하는 정책이 합리적이다.
245+
246+ ```
247+ double xProjection(Point p1, Point p2) {
248+ if(p1 == null || p2 == null){
249+ throw InvalidArgumentException("Invalid argument for MetricsCalculator.xProjection");
250+ }
251+ return (p2.x - p1.x) * 1.5;
252+ }
253+ ```
254+
0 commit comments