사용자 도구

사이트 도구


기술스터디일지

기술스터디 일지

알고리즘 (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줄 넘지 말자.
    • 블록과 들여쓰기 : if/else/while 문에 들어가는 블록은 한줄이어야 한다 - 즉, '중첩구조'가 생길 만치 함수가 커져서는 안된다. 함수에서 들여쓰기 수준이 1-2단을 넘지 않도록 하자.
  • 한가지만 해라 : TO1) 문단으로 기술할수 있도록 짜자. 지정된 함수 이름 아래에서 추상화 수준이 하나인 단계만 수행한다면 그 함수는 한가지 작업만 한다. 즉, 더이상 추상화 할수 없는 영역까지 추상화를 하자는 것이다.
  • 함수당 추상화 수준은 하나 : 함수내의 모든 추상화 수준이 동일해야 한다. 한 함수에 추상화 수준이 여럿이면 보는 사람이 헷갈린다.
    • 코드는 위에서 아래로 읽혀야 좋다. 다르게 표현하면 일련의 TO 문단을 읽듯이 프로그램이 읽혀야 한다는 의미가 된다.
  • SWITCH 문 : Switch 문(또는 if/else 가 다수 발생하는 문장)은 작게 만들기 어렵다. 이것은 저차원 클래스에 숨겨서 다형성을 이용해 처리하자. 책의 목록 3-5를 참고하면, 디자인 패턴 중 '추상 팩토리'에 숨기고 있음을 알수 있다.
  • 서술적인 이름 : 함수이름은 길어도 괜찮다. 서술적인 주석보다 낫다. 이름을 붙일때는 일관성이 있는 문체로 하자.
  • 함수 인수 : 적을 수록 좋다. 파라미터가 있는 함수는 파라미터와 함수간의 추상화 수준이 보통 다르다. 테스트할적에도 인수가 많으면 부담스럽다. 출력 인수는 입력보다 이해하기 어렵다.
    • 많이 쓰는 단항 형식 : 질문형 함수인 경우. “isString()” 같은 경우. 드문 경우지만 '이벤트'를 지칭하는 경우에는 '이벤트'임이 드러나야 된다. 이 외의 경우에는 단항 함수를 피한다. (예 : includeSetupPageInto(StringBuffer pageText)). 그러니까, 출력인수를 쓰지마라.
    • 플래그 인수 : 는 추하다. 함수로 Bool을 넘기는 관례는 정말로 끔찍하다! 함수를 나눠라. 플래그 인수는 함수가 '여러가지 일'을 한다는 것과 같다.
    • 이항 함수 : 인수가 2개인 함수는 1개인 것보다 이해하기 어렵다.
    • 인수객체 : 3항 넘어간다? 그러면 객체로 묶는걸 생각해봐라. 이는 인수에 '개념'을 표현하게 되므로 유효한 방법이 된다.
    • 인수 목록 : printf() 함수는 가변적이다. 그러나 실은 이항함수(string 고정과, 표현변수) 인것과 같음. 가변 함수는 이런 원리를 가진 경우에만 쓰도록 하자.
    • 동사와 키워드 : 좋은 함수 이름이 필요하다. 동사/명사 쌍을 이루어 함수 이름을 짓도록 하자. 함수 이름에 '키워드'가 들어가도 좋다.
  • 부수효과를 일으키지 마라! : 함수에서 한가지를 하겠다고 약속해놓고 다른일을 해? 못할 짓이다. 시간적 결합 순서 종속성 을 초래한다. 책의 예제는 '패스워드' 체크만 하겠다고 해놓고 '세션 초기화' 까지 시켜버림. 이는 시간적 결합을 초래한다. 즉, '패스워드 체크' 만 하고 싶은데, '세션 초기화' 가 가능한 상황에서만 호출할수 있음.
    • 출력 인수 : 객체지향 언어를 사용하면서는 출력 인수를 사용할 이유가 없다. 출력인수 써먹으라고 만들어진게 'this'이기 때문이다.
  • 명령과 조회를 분리 : 함수는 뭔가 수행하던지 뭔가 답하던지 둘중 하나만 해야 한다. 둘다 하면 의미가 모호해짐.
  • 오류 코드 보다 예외를 사용해라 : 명령 함수에서 오류 코드 반환하는 방식은 명령/조회 분리규칙을 미묘하게 위반한다. 차라리 Try 블럭으로 묶어서 Exception을 던지는 것이 낫다.
  • Try/Catch 블록 뽑아내기 : 그런데 그 블록은 또 '추하다' 그러니까, 별도 함수로 뽑아내는 것이 낫다.
    • 오류 처리도 한가지 '작업'이다 : 오류 처리 함수는 오류만 처리해야 한다. 에러를 ENUM 으로 뽑아내는 것은 에러 종속성을 만들어내고 에러 변경을 어렵게 한다. 오류 코드 대신 예외를 사용하면 새 예외는 Exception 클래스에서 파생되므로, 새 예외 클래스를 추가할수 있다.
  • 반복 하지 마라 : 반복은 모든 소프트웨어 악의 근원이다. 객체지향 프로그래밍은 코드를 부모 클래스로 몰아서 중복을 없앤다.
  • 구조적 프로그래밍 : '함수는 리턴문이 하나여야만 한다' 루프 안에서 break/continue 쓰지말것, goto는 절대 쓰지말것. 단, 함수가 작다면 return, break, continue를 사용해도 무방. 오히려 의도를 표현하기 용이하기도 하고.
  • 함수를 어떻게 짜죠 ? : 소프트웨어 작성 행위를 글짓기와 비슷하다고 생각해보자. 초안은 어수선하므로, 읽힐 때 까지 다듬고 고치는 과정이 필요하다.

결론 - 모든 시스템은 프로그래머가 설계한 도메인특화언어로 만들어진다. 함수는 그 안에서 동사며, 클래스는 명사. 프로그래밍의 기술은 언제나 언어 설계의 기술이다. 프로그래밍을 '언어'적으로 생각해보자.

갱신일자 : 20181201.

3회차 (2018년 12월 25일)

주석

주석을 권하지 않는다. 특히, '부정확한 주석은 아예 없는 주석보다 훨씬 나쁘다.'

  • 주석은 나쁜 코드를 보완하지 못한다 : 주석을 추가하는 이유는 대개 '코드가 나쁘기' 때문.
  • 코드로 의도를 표현해라.
  • 좋은 주석도 있긴 하다 :
    • 법적인 주석(저작권 등).
    • 정보를 제공하는 주석(추상 메서드가 반환할 값, 특정 포맷 반환시의 포맷 설명 등).
    • 의도를 설명(코드에 드러나지 않는 원작자의 '의도'를 설명하는 주석은 좋다)
    • 의미를 명료화 하는 주석 (인수나 반환값이 표준라이브러리/ 변경 불가 코드에 속한다면 의미를 밝히면 좋다)
    • 결과를 경고(실행하지 않을것을 권하는 경우, 실행한 함수가 Thread-Safe 하지 않는 등)
    • TODO 주석( 필요한데 못하는 것들. 오픈프레임 대부분의 기능들) - 이 주석은 주기적으로 점검해서 필요 없다면 없애는 것이 좋다.
    • 중요성을 강조(대수롭지 않다고 여겨질 뭔가의 중요성을 강조).
    • 공개 API의 Javadocs (공개 API 구현한다면 반드시 Javadocs를 쓰자.)
  • 나쁜 주석
    • 주절거림. 이민혁이 가졌던 나쁜 주석 습관.
    • 동어반복 (예외를 던지는 코드에 '예외를 던진다'하는 주석을 달아놓는 것은 좋은 습관이 아니다)
    • 오해할 여지가 있음.
    • 의무적으로 달아놓는 주석. (모든 함수에 Javadoc을 달 필요는 없으니 걱정마시라)
    • 이력을 기록(Git이 다 해줌)
    • 있으나 마나한 경우. (동어반복과 비슷, 생성자 함수에 생성자라고 써놓기, 개발자가 주석을 무시하게 만들어버림)
    • 함수나 변수로 표현할 수 있다면, 주석을 달지 말고 코드 표현을 바꿀것.
    • 주석을 닫는 괄호에 달지 말자. 대부분 이런 경우는 함수가 길어지기 때문에 달게 되므로, 함수를 줄이도록 노력하자.
    • 코드를 주석으로 처리하지 마라, 저자를 표시하지 마라. 버전관리툴이 알아서 다 해준다.
    • HTML 주석 쓰지마라. 최악이다!
    • 주석은 꼭 달아야 한다면 근처에만 써라. 시스템 전반 정보 기록하지 말것.
    • 비공개 코드의 Javadocs는 필요없는 것.

형식 맞추기

즉, '코드 컨벤션' 이야기.

팀으로 일한다면 코드 컨벤션을 정하고 따르자.

  • 적절한 행길이 : 반드시 지킬 규칙은 아니지만 대부분의 프로젝트는 파일 하나가 200줄 내외로 이루어져 있으므로 이를 지켰으면 한다. 작은 파일이 이해하기 쉬움.
  • 신문기사 처럼 써라 : 간단하고 설명 가능한 이름, 소스파일 첫부분엔 고차원 개념과 알고리즘 설명. 아래로 내려갈수록 의도를 상세하게. 마지막에는 가장 저차원 함수와 세부내역을 배치.
  • 개념을 빈 행으로 분리 : 각 행이 수식/절을 나타내고 일련의 행 묶음이 완결된 개념을 표현하도록 하자. 가독성에 큰 영향을. 반대로 세로 밀집도는 연관성을 의미하므로 밀접한 개념은 붙여서 표현하자.
  • 수직거리 : 서로 밀접한 개념은 '세로로 가까이' 두어야 한다. protected 변수를 피한다의 의미는 무엇이지?.. 변수는 사용위치에 최대한 가깝게 선언. 루프 제어 변수는 루프문 내부에 선언. 인스턴스 변수는 클래스 맨 처음에 선언. 종속적인 함수는 두 함수를 세로로 가깝게 배치. 이때, 호출 하는 함수를 호출 당하는 함수보다 먼저 배치한다.

가로 형식을 맞추기도 해야한다. 가로 길이는 100-120자가 안넘어가는게 좋겠다. 짧은 행이 바람직 하다.

  • 가로공백과 밀집도 : 가로로는 공백을 사용해서 밀접개념/느슨한 개념을 표현한다. '연산자'의 개념 분리를 위해 가로공백을 사용한다. 할당문은 좌/우 요소를 분명히 나눈다. 연산자 우선순위 강조를 위해서도 공백을 쓴다. 그런데 연산자를 통한 공백 분리는 IDE에서 미지원하기도 함.
  • 가로 정렬 : 별로 유용하지 않다. 이 불필요한 정렬이 의도를 가리기도 함.
  • 들여쓰기 : 구조를 한눈에 들여다 볼수 있게 해준다. 그러므로 한 행에 다쓰려고 하지 말자.

객체와 자료구조

  • 자료의 추상화 - 단순히 set/get을 각 멤버마다 만들지 말고 '추상인터페이스'를 제공하여 사용자가 구현을 모른채 자료 핵심을 조작할수 있어야 한다. 자료는 '추상적인 개념으로 표현'하는 것이 좋다.
  • 자료 / 객체 비대칭 - 객체는 자료를 다루는 함수만 공개한다. 반대로 자료구조는 자료를 그대로 공개한다. 두 정의는 본질적으로 상반된다.
    • 절차적인 코드(자료구조에 가까움)는 기존 자료구조 변경은 안하면서 새 객체(자료구조성 객체)를 추가를 할수는 있다. 그러나 새 자료구조가 필요해지게 되면, 모든 기존 메서드를 변경해야 한다, 메서드를 책임자 클래스가 모두 보유하고 있는 경우.
    • 객체 지향 코드(객체에 가까움)는 새로운 자료구조 객체를 만들기는 쉽다. 그러나 새 함수 추가하려면 모든 자료구조 객체를 변경해야 한다. 메서드를 자료구조 클래스가 보유하고 있는 경우.
  • 디미터 법칙. '모듈은 자신이 조작하는 객체의 속사정을 몰라야 한다'
    • “클래스 C의 메서드 f는 다음과 같은 객체의 메서드만 호출해야 한다 : 1. 클래스 C, 2. f가 생성한 객체, 3. f의 인자로 넘어온 객체, 4. C 인스턴스 변수에 저장된 객체”. 그러나, 위 메서드가 반환한 객체의 메서드는 직접 호출하면 안된다!
    • 그런데, 이거 때문에 절반씩 객체/자료구조인 잡종구조가 나온다. 이렇게 되면 새로운 함수만들기도 어렵고, 새로운 자료구조 추가도 어려우므로 피하는게 좋다.
  • 자료전달객체(DTO;Data Transfer Object) - 공개변수 있고 함수 없는 클래스 즉, 자료구조체. 좀더 구체화되면 'Bean'이 된다.
    • 활성레코드 - DTO의 특수 형태. Bean 에서 save/find 같은 탐색 함수를 제공하는 형태. 이것 역시도 자료구조로 취급하자(비즈니스 로직을 넣지 말자는 이야기).

오류 처리

  • 오류코드 보다 '예외'를 사용해라 - 호출자가 호출 후 바로 if절을 만들어 에러처리 해야하는 코드는 구리다. 오류처리와 헷갈리지 않도록 예외처리를 쓰는게 바람직하다.
  • Try/Catch/Finally 문 부터 작성해라 - 예외처리를 통해 프로그램 안에 '범위'를 정의한다는 점에서 가독성 높은 코드를 만들 수 있다. 그리고 이는 try 블록을 '트랜잭션'과 유사하게 만드는 측면이 있다.
    • TDD와 유사한 흐름으로 코드를 만들수 있게 된다 : 1. Try Catch 블록 정의 2. 예외 발생 여부 테스트 코드 작성과 비즈니스 코드 작성. 3. 예외를 던지는 상황을 구현, 4. 예외 범위를 좁힌다.
  • 미확인 예외의 사용 - 자바는 '확인된 예외'를 기반으로 한다. 그러나 이것은 OCP2)을 위반한다. 하위 단계에서 코드 변경시 상위메서드 변경이 발생하게 된다? - 확인된 오류 던지면 함수가 선언부에 '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초가 된다.
  • 깨끗한 테스트 코드의 유지
    • 지저분한 테스트 코드를 내놓는 것이 테스트 안하는 것보다 못하기도 함. 이런 경우는 테스트 코드의 존재가 개발팀의 불만사항이 되어버리기도 한다.
    • 테스트 코드를 막 짜도 좋다 라고 결정해버리면 안된다!
    • 테스트는 유연성, 유지보수성, 재사용성을 제공한다.
    • 깨끗한 테스트 코드의 핵심은 가독성
1)
LOGO 언어에서 모든 함수의 시작 키워드를 의미한다.
2)
Open Closed Principle
기술스터디일지.txt · 마지막으로 수정됨: 2019/01/08 22:50 저자 116.120.12.172