/C:/Users/user/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/firebase_auth-3.8.0/lib/src/firebase_auth.dart:623:25: Error: The method 'signInWithAuthProvider' isn't defined for the class 'FirebaseAuthPlatform'.
- 'FirebaseAuthPlatform' is from 'package:firebase_auth_platform_interface/src/platform_interface/platform_interface_firebase_auth.dart' ('/C:/Users/user/AppData/Local/Pub/Cache/hosted/pub.dartlang.org/firebase_auth_platform_interface-6.8.0/lib/src/platform_interface/platform_interface_firebase_auth.dart').
Try correcting the name to the name of an existing method, or defining a method named 'signInWithAuthProvider'.
await _delegate.signInWithAuthProvider(provider),
위와 같은 에러가 떴다........
내가 사용하고 있는 firebase_auth_platform_interface 버전이 이상한거 같아서(오류에는 6.8.0)으로 표시 pubspec.lock파일에 있는 버전을 6.7.0으로 낮췄더니 해결되었다.
플러터에서는 한 위젯의 여러 인스턴스를 만든다. 변경할 수 없는 위젯 인스턴스는 성능이 좋으므로 가능하면 const를 사용하는 것이 좋다. new, const 키워드를 사용하지 않으면 프레임워크가 가능한 const로 위젯을 추론하므로 크게 신경 쓰지 않아도 된다.
Widget build(BuildContext context){
return Button(
child: Text("hello"),
);
}
// 다음과 비교
Widget build(BuildContext context){
return new Button(
child: Text("hello"),
);
}
위의 Button 위젯은 new 키워드가 없고 아래의 Button은 new 키워드가 있다. 하지만 플러터가 알아서 처리하기 때문에 어떤 위젯이 상수(const)이고 아닌지 지정할 필요가 없다. 또한 클래스 인스턴스를 만들 때 new 를 사용할 필요가 없다. 이는 위젯뿐 아니라 객체에도 적용된다.
소프트웨어에서 이름은 어디나 쓰인다. 여기저기 도처에서 이름을 사용한다. 이름을 잘 지으면 여러모로 편하다.
의도를 분명히 밝혀라
좋은 이름을 지으려면 시간이 걸리지만 좋은 이름으로 절약하는 시간이 훨씬 더 많다. 그러므로 이름을 주의깊게 살펴 더 나은 이름이 떠오르면 개선해야 한다.
굵직한 질문에 모두 답할수 있어야 한다. ( 변수의 존재 이유는?, 수행 기능은?, 사용 방법은? 따로 주석이 필요한지?)
int d; // 경과 시간(단위: 날짜)
위의 이름 d는 아무 의미가 없다.
int elapsedTimeInDays;
int daysSinceCreation;
int daysSinceModification;
int fileAgeInDays;
위와 같이 의도가 드러나는 이름을 사용하면 코드 이해와 변경이 쉽다.
다음 코드는 무엇을 할까?
public List<int[]> getThem() {
List<int[]> list1 = new ArrayList<int[]>();
for (int[] x : theList)
if (x[0] == 4)
list1.add(x);
return list1;
}
코드가 하는 일을 짐작하기 어렵다. 문제는 코드의 단순성이 아니라 코드의 함축성이다. 코드 맥락이 코드 자체에 명시적으로 드러나지 않는다. 위 코드는 암암리에 독자가 다음의 정보를 안다고 가정한다.
theList에 무엇이 들었는가?
theList에서 0번째 값이 어째서 중요한가?
값 4는 무슨 의미인가?
함수가 반환하는 리스트 list1을 어떻게 사용하는가?
단순 이름을 고치는 것만으로 함수가 하는 일을 이해하기 쉬워진다.
그릇된 정보를 피하라
프로그래머는 코드에 그릇된 단서를 남겨서는 안 된다. 이는 코드의 의미를 흐린다.
예를 들어 hp라는 변수는 유닉스 변종을 가리키는 이름이기 때문에 직각삼각형의 빗변(hypotenuse)의 약어로 좋아 보일지라도 독자에게 혼란을 줄 수 있다.
서로 흡사한 이름을 사용하지 않도록 주의한다. 한 모듈에서 XYZController orEfficientHandlingOfStrings라는 이름을 사용하고, 조금 떨어진 모듈에서 XYZControllerForEfficientStorageOfStrings 을 사용하면 ..... 겁나 헷갈린다.
유사한 개념은 유사한 표기법을 사용한다. 일관성이 떨어지는 표기법은 그릇된 정보다.
의미 있게 구분하라
이름이 달라야 한다면 의미도 달라져야 한다. 컴파일러를 통과할지라도 연속된 숫자를 덧붙이거나 불용어를 추가하는 방식은 적절하지 못하다.
public static void copyChars(char a1[], char a2[]) {
for (int i = 0; i < a1.length; i++) {
a2[i] = a1[i];
}
}
함수 이름으로 source와 destination을 사용한다면 코드 읽기가 훨씬 쉬워진다.
class DtaRcrd102 {
private Date genymdhms;
private Date modymdhms;
private final String pszqint = "102";
/* ... */
};
class Customer {
private Date generationTimestamp;
private Date modificationTimestamp;
private final String recordId = "102";
/* ... */
}
위의 코드를 보면 명확해진다.
검색하기 쉬운 이름을 사용하라
문자 하나를 사용하는 이름과 상수는 텍스트 코드에서 눈에 띄지 않는다.
또한 나중에 grep을 이용한 검색에서 모든 단어가 검색되어 제대로 된 검색이 불가능해진다.
for (int j=0; j<34; j++) {
s += (t[j]*4)/5;
}
int realDaysPerIdealDay = 4;
const int WORK_DAYS_PER_WEEK = 5;
int sum = 0;
for (int j=0; j < NUMBER_OF_TASKS; j++) {
int realTaskDays = taskEstimate[j] * realDaysPerIdealDay;
int realTaskWeeks = (realTaskDays / WORK_DAYS_PER_WEEK);
sum += realTaskWeeks;
}
인코딩을 피하라
유형이나 범위 정보 까지 인코딩에 넣으면 해독도 어려워지고 또 인코딩을 익히느라 시간또한 쓰게 된다. 개발자는 할게 많다......불필요한 부담은 없애자!
헝가리식 표기법
요즘 시대는 변수이름에 타입을 인코딩할 필요가 없다(실제로 나는 사용해본적 없음). 그러므로 이름에 변수이름에 헝가리안 표기법으로 변수 타입을 넣지 않아도 된다.
멤버 변수 접두어
이전에는 멤버 변수 접두어에 m_를 붙였었나 보다. 그러지 말자.
public class Part {
private String m_dsc; // 설명 문자열
void setName(String name) {
m_dsc = name;
}
}
_________________________________________________
public class Part {
String description;
void setDescription(String description) {
this.description = description;
}
}
인터페이스 클래스와 구현 클래스
인터페이스와 구현클래스의 명명을 어떻게 할 것인가. 이건 사람마다 다르겠지만 쉽게 글쓴이는 인터페이스 뒤에 Impl이라고 붙이는 것을 선호한다고 한다.
ShapeFactoryImp나 심지어 CShapeFactory가 IShape Factory보다 좋다고 한다.
자신의 기억력을 자랑하지 마라
보통 프로그래머는 머리가 똑똑하다고 한다. 그렇다고 그들이 만능은 아니다.
클래스 이름
클래스 이름과 객체 이름은 명사나 명사구가 적합하다.
메서드 이름
메서드 이름은 동사나 동사구가 적합하다. 접근자, 변경자, 조건자는 javabean 표준에 따라 값 앞에 get,set,is를 붙인다.
기발한 이름은 피하라
이름이 너무 기발하면 공감대가 비슷한 사람만 이름을 기억해 낸다. 재미난 이름보다 명료한 이름을 선택하라
한 개념에 한 단어를 사용하라
추상적인 개념 하나에 단어 하나를 선택해 이를 고수한다.
말장난을 하지 마라
한 단어를 두 가지 목적으로 사용하지 마라. 다른 개념에 같은 단어를 사용한다면 이는 말장난에 불과하다.
해법 영역에서 가져온 이름을 사용하라
코드를 읽을 사람도 프로그래머이다. 전산 용어, 알고리즘 이름, 패턴 이름, 수학 용어를 사용하면 서로에게 친숙하다.
문제 영역에서 가져온 이름을 사용하라
적절한 프로그래밍 언어가 없다면 문제 영역에서 이름을 가져온다. 그러면 그 배경지식을 습득하면서 배울수 있다.
의미 있는 맥락을 추가하라
스스로 의미가 분명한 이름이 없지 않다. 하지만 대다수 이름은 그렇지 못하다. 그래서 클래스, 함수, 이름 공간에 넣어 맥락을 부여한다. 모든 방법이 실패하면 마지막 수단으로 접두어를 붙인다.
프로그래밍 언어에서 추상화의 수준은 점차 높아지겠지만 요구사항을 모호하게 줘도 우리 의도를
꿰뚫어 프로그램을 완벽하게 만드는건 불가능하다.
그러므로 코드는 항상 존재하리라
나쁜 코드
좋은 코드는 중요하다. 이전 버전에 있던 버그가 다음 버전에도 계속 남을 것이고 이것으로 인해
프로그램이 죽는 일도 발생할 것이며 다음 버전에끼치는 영향은 배가 아닌 제곱이 되므로
나중에 돌아와 정리하겠다고 생각하지 말고 깨끗한 코드를 짜려고 현재에 노력하자
*르블랑의 법칙! 나중은 결코 오지 않는다.
나쁜 코드로 치르는 대가
나쁜 코드는 개발 속도를 크게 떨어뜨린다.
프로젝트 초반에 번개처럼 잘 나아가다가도 나쁜 코드가 있다면 고칠 때마다 계속 시간이 늘어난다.
사람이 더 투여되더라도 생산성만 안 좋아질뿐 코드 자체가 좋아지진 않는다.
원대한 재설계의 꿈
나쁜 코드의 중첩으로 더 이상 좋아질 기미가 보이지 않아 재설계를 진행한다.
결국 타이거 팀이 만들어 진다.
타이거 팀의 진행 방향
1. 새로운 타이거 팀의 구성
2. 모두가 타이거 팀에 합류하고 싶어 하지만 유능한 사람이 차출 나머지는 현 시스템 유지보수
3. 유지보수팀과 타이거 팀의 경주 시작
4. 기존 시스템을 100% 기능 재현 할 때까지 개발 진행
하지만 기존 코드가 레거시 코드라면 결국 위의 진행 방향도 얼마나 걸릴지 알 수 없다는게 함정
태도
어째서 좋은 코드가 순식간에 나쁜 코드로 전락했는가?
1. 원래 설계를 뒤집는 방향으로 요구사항이 변해서?
2. 일정이 촉박해서?
3. 멍청한 관리자와 조급한 고객과 쓸모없는 마케팅 부서와 전화기 살균제 탓에?
아니다. 전적으로 우리(프로그래머)가 전문가 답지 못해서이다.
우리는 요구사항을 우리에 맞게 자문해줘야한다.
우리는 프로젝트 계획에 관여하여 일정을 잡아야한다.
우리는 관리자와, 고객과, 마케팅 부서에게 만든 프로그램의 정보를 제공해야 한다.
즉, 프로젝트의 모든 부분에 우리는 책임이 있다.
*나쁜 코드의 위험을 이해하지 못하는 관리자 말을 그대로 따르는 행동은 전문가답지 못하다.
원초적 난제
우리는 빨리 가려고 좋은 코드를 만드는데 시간을 들이지 않는다.
흔히 하는 착각은 프로그래머가 기한을 맞추려면 나쁜 코드를 양산할 수밖에 없다고 생각한다.
하지만 나쁜 코드가 시간을 늦춘다.
깨끗한 코드라는 예술?
그림을 그리는 행위와 비슷하다. 잘 그린 그림을 구분하는 능력이 그림을 잘 그리는 능력이 아닌것처럼
깨끗한 코드와 나쁜 코드를 구분할 줄 안다고 해서 깨끗한 코드를 작성할 줄 아는 뜻은 아니다.
절제와 규율을 적용해 나쁜 코드를 좋은 코드로 바꾸는 '코드 감각'이 있어야한다.
나는 우아하고 효율적인 코드를 좋아한다. 논리가 간당해야 버그가 숨어들지 못한다. 논리가 간단해야 버그가 숨어들지 못한다. 의존성을 최대한 줄여야 유지보수가 쉬워진다. 오류는 명백한 전략에 의거해 철저히 처리한다. 성능을 최적으로 유지해야 사람들이 원칙 없는 최적화로 코드를 망치려는 유혹에 빠지지 않는다. 깨끗한 코도는 한 가지를 제대로 한다.
-비야네(c++창시자이자 The c++ Programming Language 저자)
비야네는 효율을 중요하게 여긴다 하지만 글을 보면 우아한! 보기에 즐거운 이라는 표현을 쓴다. 즉 효율이라는 단어가 단순히 속도만을 뜻하는건 아니라는 뜻이다.
유혹이라는 단어도 잘 주의해서 봐야 한다. 나쁜 코드는 나쁜 코드를 유혹한다!
실용주의 프로그래머 데이브 토마스와 앤디 헌트는 이를 깨진 창문에 비유했다.
비야네는 철저한 오류 처리도 언급한다. 세세한 사항까지 꼼꼼하게 신경 쓰라는 말이다.
메모리 누수, 경쟁 상태, 일관성 없는 명명법 같이 사소하지만 세세한 사항도 신경써야 한다.
(지옥의 명명법.....;;;;;;)
마지막으로 비야네는 깨끗한 코드란 한 가지를 잘 한다고 단언한다. 수많은 저술가들이 나름의 깨끗한 코드를 가지고 있지만 깨끗한 코드는 한 가지에 '집중'한다는 것이다.
깨끗한 코드는 단순하고 직접적이다. 깨끗한 코드는 잘 쓴 문장처럼 읽힌다. 깨끗한 코드는 결코 설계자의 의도를 숨기지 않는다. 오히려 명쾌한 추상화와 단순한 제어문으로 가득하다.
-그래디 부치(Object Oriented Analysis and Design with Application 저자)
그래디는 비야네와 흡사한 의견을 표명하지만 가독성을 강조한다. 좋은 소설과 마찬가지로 깨끗한 코드는 해결할 문제의 긴장을 명확히 드러내야 한다.코드는 추측이 아니라 사실에 기반해야 한다. 반드시 필요한 내용만 담아야 한다. 코드를 읽는 사람에게 프로그래머가 단호하다는 인상을 줘야 한다.
깨긋한 코드는 작성자가 아닌 사람도 읽기 쉽고 고치기 쉽다. 단위 테스트 케이스와 인수 테스트 케이스가 존재한다. 깨끗한 코드에는 의미 있는 이름이 붙는다. 특정 목적을 달성하는 방법은 하나만 제공한다. 의존성은 최소이며 각 의존성을 명확히 정의한다. API는 명확하며 최소로 줄였다. 언어에 따라 필요한 모든 정보를 코드만으로 명확히 표현할 수 없기에 코드는 문학적으로 표현해야 마땅하다.
-Big 데이브 토마스(OTI 창립자이자 이클립스 전략의 대부)
빅 데이브는 가독성을 강조하지만 한 가지 중요한 반전을 더한다. 다른 사람이 고치기 쉽다는 것이다. 데이브는 테스트 케이스와 깨끗한 코드를 연관짓는다. TDD(테스트 주도 개발)가 중요해진 만큼 테스트 케이스가 없으면 깨끗한 코드는 나올수 없다. 데이브는 최소 즉 작은 코드와 문학적 즉 읽기 좋은 코드를 작성하나는 것을 강조한다.
깨긋한 코드의 특징은 많지만 그 중에서도 모두를 아우르는 특징이 하나 있다. 깨끗한 코드는 언제나 누군가 주의 깊게 짰다는 느낌을 준다. 고치려고 살펴봐도 딱히 손 댈 곳이 없다. 작성자가 이미 모든 사항을 고려했으므로, 고칠 궁리를 하다보면 언제나 제자리로 돌아온다. 그리고는 누군가 남겨준 코드, 누군가 주의 깊게 짜놓은 작품에 감사를 느낀다.
-마이클 페더스(Working Effectively with Legacy Code의 저자)
한 마디로 요약하면 '주의'다. 깨끗한 코드는 주의 깊게 작성한 코드다.누군가 시간을 들여 깔끔하고 단정하게 정리한 코드다. 세세한 사항까지 꼼꼼하게 신경쓴 코드다. 주의를 기울인 코드다.
최근 들어 나는 켄트 벡이 제안한 단순한 코드 규칙으로 구현을 시작한다. (그 리고 같은 규칙으로 구현을 거의 끝낸다.) 중요한 순으로 나열하자면 간단한 코 드는
· 모든 테스트를 통과한다.
· 중복이 없다.
· 시스템 내 모든 설계 아이디어를 표현한다. · 클래스, 메서드, 함수 등을 최대한 줄인다.
물론 나는 주로 중복에 집중한다. 같은 작업을 여러 차례 반복한다면 코드가 아 이디어를 제대로 표현하지 못한다는 증거다. 나는 문제의 아이디어를 찾아내 좀 더 명확하게 표현하려 애쓴다.
내게 있어 표현력은 의미 있는 이름을 포함한다. 보통 나는 확정하기 전에 이 름을 여러 차례 바꾼다. 이클립스와 같은 최신 개발 도구는 이름을 바꾸기가 상 당히 쉽다. 그래서 별 고충 없이 이름을 바꾼다. 하지만 표현력은 이름에만 국한되지 않는다. 나는 여러 기능을 수행하는 객체나 메서드도 찾는다. 객체가 여러 기능을 수행한다면 여러 객체로 나눈다. 메서드가 여러 기능을 수행한다면 메서 드 추출Extract Method 리팩터링 기법을 적용해 기능을 명확히 기술하는 메서드 하 나와 기능을 실제로 수행하는 메서드 여러 개로 나눈다.
중복과 표현력만 신경 써도 (내가 생각하는) 깨끗한 코드라는 목표에 성큼 다 가선다. 지저분한 코드를 손볼 때 이 두 가지만 고려해도 코드가 크게 나아진다. 하지만 나는 한 가지를 더 고려한다. 이는 설명하기 조금 까다롭다.
오랜 경험 끝에 나는 모든 프로그램이 아주 유사한 요소로 이뤄진다는 사실을 깨달았다. 한 가지 예가 ‘집합에서 항목 찾기’다. 직원 정보가 저장된 데이터베이 스든, 키/값 쌍이 저장된 해시 맵이든, 여러 값을 모아놓은 배열이든, 프로그램을 짜다 보면 어떤 집합에서 특정 항목을 찾아낼 필요가 자주 생긴다. 이런 상황이 발생하면 나는 추상 메서드나 추상 클래스를 만들어 실제 구현을 감싼다. 그러면 여러 가지 장점이 생긴다. 이제 실제 기능은 아주 간단한 방식으로, 예를 들어 해시 맵으로, 구현해도 괜 찮다. 다른 코드는 추상 클래스나 추상 메서드가 제공하는 기능을 사용하므로 실 제 구현은 언제든지 바꿔도 괜찮다. 지금은 간단하게 재빨리 구현했다가 나중에 필요할 때 바꾸면 된다. 게다가 집합을 추상화하면 ‘진짜’ 문제에 신경 쓸 여유가 생긴다. 간단한 찾기 기능이 필요한데 온갖 집합 기능을 구현하느라 시간과 노력을 낭비할 필요가 없 어진다. 중복 줄이기, 표현력 높이기, 초반부터 간단한 추상화 고려하기. 내게는 이 세 가지가 깨끗한 코드를 만드는 비결이다.
-론 제프리스(Extreme Progamming Installed와 Extreme Programming Adventure in c#의 저자)
결론은 중복을 피하라. 한기능만 수행해라. 제대로 표현하라. 작게 추상화해라.
코드를 읽으면서 짐작했던 기능을 각 루틴이 그 대로 수행한다면 깨끗한 코드라 불러도 되겠다. 코드가 그 문제를 풀기 위한 언어처럼 보인다면 아름다운 코드라 불러도 되겠다.
-워드 커닝햄Ward Cunningham (위키Wiki 창시자, 피트Fit 창시자, 익스트림 프로그래밍eXtreme Programming 공동 창시자, 디자인 패턴을 뒤에서 움직이는 전문가, 스몰토크Smalltalk와 객체 지향OO의 정신적 지도자, 코드를 사랑하는 프로그래머들의 대부 )
명백하고 단순해 마음이 끌리는 코드가 깨끗한 코드다. 너무도 잘 짜놓은 코드라 읽는 이가 그 사실을 모르고 넘어간다.
우리들 생각
절대적으로 옳은 것은 없다. 이 책은 오브젝트 멘토 진영이 생각하는 깨끗한 코드를 설명한다.
다트 프로그램 처럼 앱도 main 함수가 진입점이다. 플러터에서는 runApp이라는 메서드로 최상위 위젯을 감싼다. 극단적으로 이 한 행의 코드만으로도 앱을 만들 수 있다. 기능이 다양한 앱은 main 함수에서 더 많은 작업을 수행한다. 하지만 이런 앱도 반드시 최상위 위젯을 runApp의 인수로 전달해 호출해야 한다.
코드의 품질이 나아졌다. 알고리즘과 오류 처리 부분을 분리했기 때문이다. 이제는 각 개념을 독립적으로 살펴 보고 이해할 수 있다.
Try-Catch-Finally 문부터 작성하다.
어떤 면에서 try 블록은 트랜잭션과 비슷하다. try 블록에서 무슨 일이 생기든지 catch 블록은 프로그램 상태를 일관성 있게 유지해야 한다. 그러므로 예외가 발생할 코드를 짤 때는 rry-catch-finally 문으로 시작하는 편이 낫다. 그러면 try 블록에서 무슨 일이 생기든지 호출자가 기대하는 상태를 정의하기 쉬워진다.
public List<RecordedGrip> retrieveSection(String sectionName) {
// 실제로 구현할 때까지 비어 있는 더미를 반환한다.
return new ArrayList<RecordedGrip>();
}
아래 코드는 예외를 던진다.
public List<RecordedGrip> retrieveSection(String sectionName) {
try {
FileInputStream stream = new FileInputStream(sectionName)
} catch (Exception e) {
throw new StorageException("retrieval error", e);
}
return new ArrayList<RecordedGrip>();
}
public List<RecordedGrip> retrieveSection(String sectionName) {
try {
FileInputStream stream = new FileInputStream(sectionName);
stream.close();
} catch (FileNotFoundException e) {
throw new StorageException("retrieval error", e);
}
return new ArrayList<RecordedGrip>();
}
미확인 예외를 사용하라
여러 해 동안 자바 프로그래머들은 확인된 예외의 장단점을 놓고 논쟁을 벌여왔다. 메서드를 선언할 때는 ㅔㅁ서드가 반환할 예외를 모두 열거했다. 게다가 메서드가 반환하는 예외는 메서드 유형의 일부였다. 코드가 메서드를 사용하는 방식이 메서드 선언과 일치하지 않으면 아예 컴파일도 못했다.
확인된 예외는 OCP(open closed principle)을 위반한다. 메서드에서 확인된 예외를 던졌는데 catch 블록이 세단계 위에 있다면 그 사이 메서드 모두가 선언부에 해당 예외를 정의해야 한다. 즉, 하위 단계에서 코드를 변경하면 상위 단계 메서드 선언부를 전부 고쳐야 한다는 말이다.
예외에 의미를 제공하라
예외를 던질 때는 전후 상황을 충분히 덧붙인다. 그러면 오류가 발생한 원인과 위치를 찾기가 쉬워진다. 자바는 모든 예외에 호출 스택을 제공한다. 하지만 실패한 코드의 의도를 파악하려면 호출 스택만으로 부족하다.
오류 메시지에 정보를 담아 예외와 함께 던진다. 실패한 연산 이름과 실패 유형도 언급한다. 애플리케이션이 로깅 기능을 사용한다면 catch블록에서 오류를 기록하도록 충분한 정보를 넘겨준다.
호출자를 고려해 예외 클래스를 정의하라
오류를 분류하는 방법은 수없이 많다. 오류가 발생한 위치로 분류가 가능하다. 예를 들어, 오류가 발생한 컴포넌트로 분류한다. 아니면 유형으로도 분류가 가능하다.
감싸기 기법을 사용하면 특정 업체가 API를 설계한 방식에 발목 잡히지 않는다. 프로그램이 사용하기 편리한 API를 정의하면 그만이다.
흔히 예외 클래스가 하나만 있어도 충분한 코드가 많다. 예외 클래스에 포함된 정보로 오류를 구분해도 괜찮은 경우가 그렇다. 한 예외는 잡아내고 다른 예외는 무시해도 괜찮은 경우라면 여러 예외 클래스를 사용한다.
정상 흐름을 정의하라
위의 흐름대로 예외를 정의하다 보면 오류 감지가 프로그램 언저리로 밀려난다. 외부 API를 감싸 독자적인 예외를 던지고, 코드 위에 처리기를 정의해 중단된 계산을 처리한다. 대개는 멋진 처리 방식이지만, 때로는 중단이 적합하지 않은 때도 있다.
null을 반환하지 마라
오류 처리를 논하는 장이라면 우리가 흔히 저지르는 바람에 오류를 유발하는 행위도 언급해야 한다고 생각한다. 그 중 첫째가 null을 반환하는 습관이다.
public void registerItem(Item item) {
if (item != null) {
ItemRegistry registry = peristentStore.getItemRegistry();
if (registry != null) {
Item existing = registry.getItem(item.getID());
if (existing.getBillingPeriod().hasRetailOwner()) {
existing.register(item);
}
}
}
}
위 코드는 두번째 행에서 null확인이 누락된다.
null을 전달하지 마라
정상적인 인수로 null을 기대하는 API가 아니라면 메서드로 null을 전달하는 더 나쁘다. 정상적인 인수로 null을 기대하는 API가 아니라면 메서드로 null을 전달하는 코드는 최대한 피한다.
public class MetricsCalculator
{
public double xProjection(Point p1, Point p2) {
return (p2.x – p1.x) * 1.5;
}
...
}
인수로 누군가 null을 전달하면??
당연히 NullPointException이 발생한다.
대다수 프로그래밍 언어는 호출자가 실수로 넘기는 null을 적절히 처리하는 방법이 없다. 그렇다면 애초에 null을 넘기지 못하도록 금지하는 정책이 합리적이다.
결론
깨끗한 코드는 읽기도 좋아야 하지만 안정성도 높아야 한다. 이 둘은 상충하는 목표가 아니다. 오류 처리를 프로그램 논리와 분리해 독자적인 사안으로 고려하면 튼튼하고 깨끗한 코드를 작성할 수 있다. 오류 처리를 프로그램 논리와 분리하면 독립적인 추론이 가능해지며 코드 유지보수성도 크게 높아진다.