기술스터디 일지
알고리즘 (2018년 11월 4일부터)
1회차 (2018년 11월 4일)
2회차 (2018년 11월 10일 )
찾아볼 것 : 점화식, P 문제, NP 문제, NP-난해 문제, NP-완전 문제 등.
3회차 (2018년 11월 25일 )
클린코드(2018년 11월 11일부터)
1회차 (2018년 11월 11일)
개발자는 '나쁜 코드' 에 대해 전적으로 책임이 있음. 나쁜 코드의 위험을 이해하지 못하는 관리자의 말을 그대로 따르는 행동은 전문가 답지 못하다.
깨끗한 코드를 항시 유지할 것 :중복을 피하라, 한 기능만 수행해라, 제대로 표현하라, 작게 추상화하라.
한번 커밋 할 때 좀더 깨끗한 코드를 커밋하면 코드는 나빠지지 않는다.
이름짓기
의도를 분명하게 밝혀라: 변수명도 'i, j, d' 같은 내용보다는 'elapsedTimeInDays' 같은 형태를 써라. 코드 맥락을 명시적으로 드러내라.
그릇된 정보를 피하라 : 의미가 혼동될수 있는 약자를 쓰지 말자. 일관성을 떨어뜨리는 표기법은 그릇된 정보다. (이는 IDE의 자동완성 기능을 사용할 때에 혼동을 막기위해서라도 필요함.)
의미있게 구분하라 : 변수 이름에 a1, a2, …, aN 등의 이름을 쓰는것은 의미를 알기 어렵다. 개념이 달라지면 이름도 달라져야 한다. 'Info, Data, 정관사' 등의 단어는 웬만하면 쓰지 말자. 변수 이름으로 'variable'을 사용하지 말자.
발음하기 쉬운 이름을 사용 하자 : 메인 프레임 세계에서는 쉽지 않아 보임… (약자로 써서 발음하기 어려운 것들을 설명함).
검색하기 쉬운 이름을 써라 : 'grep' 쓰기에 편리함. 이름 길이는 범위 크기에 비례해야 한다
인코딩을 피하라 : 인코딩한 이름은 써먹지 말자. 이제 '헝가리안 표기법' 이나 '멤버변수 접두어(예: m_으로 시작하는 것들)' 에 의미를 두지 않아도 좋다.
자신의 기억력을 자랑하지 마라 : '명료함이 최고' 라는 원칙을 명심할것.
클래스 이름은 명사 나 명사구 가 적합하다. Manager, Processor, Data, Info 같은 단어는 피하자. 동사는 쓰지말자.
메서드 이름은 동사 나 동사구 가 적합하다, 접근자/변경자/조건자는 get/set/is 를 붙여 써먹자.
기발한 이름을 피하자. 자기의 재치를 이름짓기로 과시하지 말자. 의도를 분명하고 솔직하게.
한개념에 한단어를 사용하라. fetch / retrieve / get가 혼재하면 위험하다. controller/manager/driver 를 섞어 쓰면 혼란스럽다. 코드를 일관성 있게.
말장난을 하지 말자 : 위의 내용과는 반대로 한단어를 두개념으로 쓰지 말자는 이야기.
해법 영역에서 가져온 이름을 사용하라? : '기술개념' 영역에 존재하는 이름이라면, 그 이름을 선택해서 쓰자. (예: VISITOR 패턴에 익숙하다면 AccountVisitor라고 짓기).
문제 영역에서 가져온 이름을 사용하라? : 위의 '기술개념' 영역에 존재하는 이름이 없다 판단 된다면 문제 영역에서 쓰자.
의미있는 맥락을 추가하자 : 다 안되면 구분 가능한 의미의 단어를 추가하기로 하자. 예를들어, firstName, lastName, street 이런 것들의 '주소' 라면, 앞에 'addr'을 추가하는 식.
불필요한 맥락은 빼자 : 어플리케이션 이름때문에 이 이름을 '접두어'로 넣겠다는 생각은 좋은 생각이 아니다. C++이라면 네임 스페이스를 쓰자.
좋은 이름을 선택하려면 설명능력이 뛰어나야하고 문화적 배경이 같아야 한다. 암기는 IDE에 바꾸고 좋은 이름으로 적극적으로 바꾸자.
2장 까지(p.38) 읽음.
2회차 (2018년 11월 14일)
함수
SRP;Single Responsibility Principle, OCP;Open Closed Principle 에 연관한다.
작게 만들어라 : 함수는 작게 만들자. 가로 150자 넘지말자. 함수 100줄 넘지 말자.
한가지만 해라 : TO
1) 문단으로 기술할수 있도록 짜자. 지정된 함수 이름 아래에서 추상화 수준이 하나인 단계만 수행한다면 그 함수는 한가지 작업만 한다. 즉, 더이상 추상화 할수 없는 영역까지 추상화를 하자는 것이다.
함수당 추상화 수준은 하나 : 함수내의 모든 추상화 수준이 동일해야 한다. 한 함수에 추상화 수준이 여럿이면 보는 사람이 헷갈린다.
SWITCH 문 : Switch 문(또는 if/else 가 다수 발생하는 문장)은 작게 만들기 어렵다. 이것은 저차원 클래스에 숨겨서 다형성을 이용해 처리하자. 책의 목록 3-5를 참고하면, 디자인 패턴 중 '추상 팩토리'에 숨기고 있음을 알수 있다.
서술적인 이름 : 함수이름은 길어도 괜찮다. 서술적인 주석보다 낫다. 이름을 붙일때는 일관성이 있는 문체로 하자.
함수 인수 : 적을 수록 좋다. 파라미터가 있는 함수는 파라미터와 함수간의 추상화 수준이 보통 다르다. 테스트할적에도 인수가 많으면 부담스럽다. 출력 인수는 입력보다 이해하기 어렵다.
많이 쓰는 단항 형식 : 질문형 함수인 경우. “isString()” 같은 경우. 드문 경우지만 '이벤트'를 지칭하는 경우에는 '이벤트'임이 드러나야 된다. 이 외의 경우에는 단항 함수를 피한다. (예 : includeSetupPageInto(StringBuffer pageText)). 그러니까, 출력인수를 쓰지마라.
플래그 인수 : 는 추하다. 함수로 Bool을 넘기는 관례는 정말로 끔찍하다! 함수를 나눠라. 플래그 인수는 함수가 '여러가지 일'을 한다는 것과 같다.
이항 함수 : 인수가 2개인 함수는 1개인 것보다 이해하기 어렵다.
인수객체 : 3항 넘어간다? 그러면 객체로 묶는걸 생각해봐라. 이는 인수에 '개념'을 표현하게 되므로 유효한 방법이 된다.
인수 목록 : printf() 함수는 가변적이다. 그러나 실은 이항함수(string 고정과, 표현변수) 인것과 같음. 가변 함수는 이런 원리를 가진 경우에만 쓰도록 하자.
동사와 키워드 : 좋은 함수 이름이 필요하다. 동사/명사 쌍을 이루어 함수 이름을 짓도록 하자. 함수 이름에 '키워드'가 들어가도 좋다.
부수효과를 일으키지 마라! : 함수에서 한가지를 하겠다고 약속해놓고 다른일을 해? 못할 짓이다. 시간적 결합 순서 종속성 을 초래한다. 책의 예제는 '패스워드' 체크만 하겠다고 해놓고 '세션 초기화' 까지 시켜버림. 이는 시간적 결합을 초래한다. 즉, '패스워드 체크' 만 하고 싶은데, '세션 초기화' 가 가능한 상황에서만 호출할수 있음.
명령과 조회를 분리 : 함수는 뭔가 수행하던지 뭔가 답하던지 둘중 하나만 해야 한다. 둘다 하면 의미가 모호해짐.
오류 코드 보다 예외를 사용해라 : 명령 함수에서 오류 코드 반환하는 방식은 명령/조회 분리규칙을 미묘하게 위반한다. 차라리 Try 블럭으로 묶어서 Exception을 던지는 것이 낫다.
Try/Catch 블록 뽑아내기 : 그런데 그 블록은 또 '추하다' 그러니까, 별도 함수로 뽑아내는 것이 낫다.
반복 하지 마라 : 반복은 모든 소프트웨어 악의 근원이다. 객체지향 프로그래밍은 코드를 부모 클래스로 몰아서 중복을 없앤다.
구조적 프로그래밍 : '함수는 리턴문이 하나여야만 한다' 루프 안에서 break/continue 쓰지말것, goto는 절대 쓰지말것. 단, 함수가 작다면 return, break, continue를 사용해도 무방. 오히려 의도를 표현하기 용이하기도 하고.
함수를 어떻게 짜죠 ? : 소프트웨어 작성 행위를 글짓기와 비슷하다고 생각해보자. 초안은 어수선하므로, 읽힐 때 까지 다듬고 고치는 과정이 필요하다.
결론 - 모든 시스템은 프로그래머가 설계한 도메인특화언어로 만들어진다. 함수는 그 안에서 동사며, 클래스는 명사. 프로그래밍의 기술은 언제나 언어 설계의 기술이다. 프로그래밍을 '언어'적으로 생각해보자.
갱신일자 : 20181201.
3회차 (2018년 12월 25일)
주석
주석을 권하지 않는다. 특히, '부정확한 주석은 아예 없는 주석보다 훨씬 나쁘다.'
나쁜 주석
주절거림. 이민혁이 가졌던 나쁜 주석 습관.
동어반복 (예외를 던지는 코드에 '예외를 던진다'하는 주석을 달아놓는 것은 좋은 습관이 아니다)
오해할 여지가 있음.
의무적으로 달아놓는 주석. (모든 함수에 Javadoc을 달 필요는 없으니 걱정마시라)
이력을 기록(Git이 다 해줌)
있으나 마나한 경우. (동어반복과 비슷, 생성자 함수에 생성자라고 써놓기, 개발자가 주석을 무시하게 만들어버림)
함수나 변수로 표현할 수 있다면, 주석을 달지 말고 코드 표현을 바꿀것.
주석을 닫는 괄호에 달지 말자. 대부분 이런 경우는 함수가 길어지기 때문에 달게 되므로, 함수를 줄이도록 노력하자.
코드를 주석으로 처리하지 마라, 저자를 표시하지 마라. 버전관리툴이 알아서 다 해준다.
-
주석은 꼭 달아야 한다면 근처에만 써라. 시스템 전반 정보 기록하지 말것.
비공개 코드의 Javadocs는 필요없는 것.
형식 맞추기
즉, '코드 컨벤션' 이야기.
팀으로 일한다면 코드 컨벤션을 정하고 따르자.
적절한 행길이 : 반드시 지킬 규칙은 아니지만 대부분의 프로젝트는 파일 하나가 200줄 내외로 이루어져 있으므로 이를 지켰으면 한다. 작은 파일이 이해하기 쉬움.
신문기사 처럼 써라 : 간단하고 설명 가능한 이름, 소스파일 첫부분엔 고차원 개념과 알고리즘 설명. 아래로 내려갈수록 의도를 상세하게. 마지막에는 가장 저차원 함수와 세부내역을 배치.
개념을 빈 행으로 분리 : 각 행이 수식/절을 나타내고 일련의 행 묶음이 완결된 개념을 표현하도록 하자. 가독성에 큰 영향을. 반대로 세로 밀집도는 연관성을 의미하므로 밀접한 개념은 붙여서 표현하자.
수직거리 : 서로 밀접한 개념은 '세로로 가까이' 두어야 한다. protected 변수를 피한다의 의미는 무엇이지?.. 변수는 사용위치에 최대한 가깝게 선언. 루프 제어 변수는 루프문 내부에 선언. 인스턴스 변수는 클래스 맨 처음에 선언. 종속적인 함수는 두 함수를 세로로 가깝게 배치. 이때, 호출 하는 함수를 호출 당하는 함수보다 먼저 배치한다.
가로 형식을 맞추기도 해야한다. 가로 길이는 100-120자가 안넘어가는게 좋겠다. 짧은 행이 바람직 하다.
가로공백과 밀집도 : 가로로는 공백을 사용해서 밀접개념/느슨한 개념을 표현한다. '연산자'의 개념 분리를 위해 가로공백을 사용한다. 할당문은 좌/우 요소를 분명히 나눈다. 연산자 우선순위 강조를 위해서도 공백을 쓴다. 그런데 연산자를 통한 공백 분리는 IDE에서 미지원하기도 함.
가로 정렬 : 별로 유용하지 않다. 이 불필요한 정렬이 의도를 가리기도 함.
들여쓰기 : 구조를 한눈에 들여다 볼수 있게 해준다. 그러므로 한 행에 다쓰려고 하지 말자.
객체와 자료구조
자료의 추상화 - 단순히 set/get을 각 멤버마다 만들지 말고 '추상인터페이스'를 제공하여 사용자가 구현을 모른채 자료 핵심을 조작할수 있어야 한다. 자료는 '추상적인 개념으로 표현'하는 것이 좋다.
자료 / 객체 비대칭 - 객체는 자료를 다루는 함수만 공개한다. 반대로 자료구조는 자료를 그대로 공개한다. 두 정의는 본질적으로 상반된다.
절차적인 코드(자료구조에 가까움)는 기존 자료구조 변경은 안하면서 새 객체(자료구조성 객체)를 추가를 할수는 있다. 그러나 새 자료구조가 필요해지게 되면, 모든 기존 메서드를 변경해야 한다, 메서드를 책임자 클래스가 모두 보유하고 있는 경우.
객체 지향 코드(객체에 가까움)는 새로운 자료구조 객체를 만들기는 쉽다. 그러나 새 함수 추가하려면 모든 자료구조 객체를 변경해야 한다. 메서드를 자료구조 클래스가 보유하고 있는 경우.
디미터 법칙. '모듈은 자신이 조작하는 객체의 속사정을 몰라야 한다'
“클래스 C의 메서드 f는 다음과 같은 객체의 메서드만 호출해야 한다 : 1. 클래스 C, 2. f가 생성한 객체, 3. f의 인자로 넘어온 객체, 4. C 인스턴스 변수에 저장된 객체”. 그러나, 위 메서드가 반환한 객체의 메서드는 직접 호출하면 안된다!
그런데, 이거 때문에 절반씩 객체/자료구조인 잡종구조가 나온다. 이렇게 되면 새로운 함수만들기도 어렵고, 새로운 자료구조 추가도 어려우므로 피하는게 좋다.
자료전달객체(DTO;Data Transfer Object) - 공개변수 있고 함수 없는 클래스 즉, 자료구조체. 좀더 구체화되면 'Bean'이 된다.
오류 처리
오류코드 보다 '예외'를 사용해라 - 호출자가 호출 후 바로 if절을 만들어 에러처리 해야하는 코드는 구리다. 오류처리와 헷갈리지 않도록 예외처리를 쓰는게 바람직하다.
Try/Catch/Finally 문 부터 작성해라 - 예외처리를 통해 프로그램 안에 '범위'를 정의한다는 점에서 가독성 높은 코드를 만들 수 있다. 그리고 이는 try 블록을 '트랜잭션'과 유사하게 만드는 측면이 있다.
미확인 예외의 사용 - 자바는 '확인된 예외'를 기반으로 한다. 그러나 이것은 OCP
2)을 위반한다. 하위 단계에서 코드 변경시 상위메서드 변경이 발생하게 된다? - 확인된 오류 던지면 함수가 선언부에 'throws' 구문을 추가해야 한다. 변경된 함수 모두가 catch 블럭을 두거나, throws 절을 모두 추가해야 된다는 이야기. 즉, 캡슐화를 깨먹어버림.
예외에 의미를 제공해라 - 예외를 던질 때엔 전후 상황을 덧붙인다. 그러면 오류 발생원인과 위치 파악이 용이해진다. 자바는 모든 예외에 호출 스택 제공하지만, 이것만으로는 의도를 알수 없다.
호출자를 고려한 예외 클래스 정의 - 오류를 잡아내는 방법을 프로그래머가 제일 고민하므로, 이것이 중심으로 오류를 분류하는 것이 좋다. 외부 API는 감싸기 기법을 써서 오류 처리를 하도록 하자.
정상 흐름을 정의하자 - 비즈니스/오류 처리를 분리할 수 있다면 될것. 즉, 오류처리 영역에서 비즈니스 로직을 가지지 않도록 하자.
NULL 반환하지 마라, 전달하지도 말라 : null체크하는 로직이 들어갈일이 없도록 만들자. null을 인자로 전달하는 코드도 최대한 피하자. (null을 전달해야 하는 함수는 제외). null체크에 assert문을 쓸 수도 있다. 그러니까, 정책적으로 이걸 못전달하게 컴파일 단계에서 체크할 방법은 없으니 아예 정책적으로 null을 못쓰게 하자.
깨끗한 코드는 읽기도 좋아야 하지만 안정성도 높아야 한다. 이 둘은 상충하는 목표가 아니다.
4회차 (2019년 1월 1일)
경계
외부코드와 우리 코드를 깔끔하게 Mash-Up 하는 방법을 논한다.
외부 코드 사용하기 : 책에서는 특정한 객체를 Map 컨테이너를 사용한다고 할때, 이것을 한번 감싸는 인터페이스를 한번 더 구현해서 경계를 지어주길 바라고 있음. 이는, 단순히 Map을 사용자 코드 내에서 객체로 넘기게 되면, Map 의 불필요한 기능까지도 넘겨지게 되어 인터페이스 사용자가 잘못 사용할 가능성이 높아지기 때문임. 요점은, 경계 인터페이스를 여기저기 넘기지 말라는 것.
경계를 살피고 익히기 : API를 사용하여 우리 인터페이스에 통합할수 있는지 테스트 한다. 이런 테스트를 '학습테스트'라고 한다.
log4j 익히기 : 위의 '경계를 살피고 익히기'를 위한 Java측의 솔루션 중 하나.
학습테스트의 효과 : API를 익힌다면, 실제 코드에 바로 녹여내는것보다 학습테스트 케이스를 만드는것이 훨씬더 안전하다.
아직 존재하지 않는 코드의 사용 : 모르는 코드 영역을 감싸고 자체적인 인터페이스를 구현한다. 이렇게 하면 의도가 분명해진다. 이렇게 설계하면 테스트도 아주 편하다.
깨끗한 경계 : 이렇게 깔끔하게 분리해놓으면 변경에도 유연하게 대처할 수 있다.
외부 패키지를 호출하는 코드를 가능한 줄여서 경계를 관리하자. Map 을 감싸거나, 어뎁터 패턴을 쓰거나. 높은 코드 가독성, 일관성 높아진다. 외부 인터페이스 변경시 보수해야 하는 코드가 줄어듬은 덤이다.
5회차 (2019년 1월 8일)
단위 테스트
법칙
1. 실패하는 단위테스트를 작성할때 까지 실제 코드 작성하지 않음.
2. 컴파일 실패 안하면서 실행이 실패하는 정도로만 단위테스트
3. 현 실패하는 테스트를 통과할 정도로 실제 코드 작성.
이 규칙을 따르면 개발/테스트 주기가 30초가 된다.
깨끗한 테스트 코드의 유지
지저분한 테스트 코드를 내놓는 것이 테스트 안하는 것보다 못하기도 함. 이런 경우는 테스트 코드의 존재가 개발팀의 불만사항이 되어버리기도 한다.
테스트 코드를 막 짜도 좋다 라고 결정해버리면 안된다!
테스트는 유연성, 유지보수성, 재사용성을 제공한다.
깨끗한 테스트 코드의 핵심은 가독성