해당 내용은 김진태님의 강의에서 배운 내용을 요약 한 것입니다.
요약된 내용보다 강의내용은 훨씬 방대하고, 훌륭했음을 미리 알려드립니다.


코드품질 확보가 왜 필요한가?

<문제 Case1>

<문제 Case2>

<문제 Case3>

즉, 코드품질이 확보되지 않으면 나쁜코드 양산의 악순환이 발생하고, 나쁜코드는 다음과 같은 악순환을 야기한다.

  1. 나쁜코드 양산
  2. 변경시 엉뚱한 곳에서 문제 발생
  3. 생산성 저하
  4. 프로젝트 인력 추가 투입
  5. 시스템 설계를 이해하지 못함
  6. 설계 의도와 상반되는 변경 코드 작성

소스코드 품질을 확보하기 위해서 어떻게 할 수 있는가?

  1. 클린코드 : 코드를 사람이 이해할 수 있도록 작성 및 개선하는 활동
  2. 코드리뷰 : 프로젝트 참여자들이 소스코드에 대해 토론하고, 잠재된 결함을 찾는 활동
  3. 리팩토링 : 결함제거, 코드 및 아키텍처 구조 개선 등 기능의 변경없이 코드를 개선하는 활동
  4. 단위 테스트 : 작성된 함수/메소드가 정사적인 동작을 수행하는지 확인하기 위해 테스트 코드 작성 및 결함 수정을 수행하는 활동
  5. 정적분석 : 소스코드를 분석하는 도구를 이용하여 컴파일 단계에서 잠재된 결함을 찾는 활동

ch01. 클린코드

사람이 쉽게 이해할 수 있도록 코드를 작성하고, 지속적으로 개선하는 활동

비야네 스트롭스트룹(C++창시자) : 나는 우아하고 효율적인 코드를 좋아한다. 논리가 간단해야 버그가 숨어들지 못한다. 의존성을 최대한 줄여야 유지보수가 쉬워진다. 오류는 명백한 전략에 의거해 철저히 처리한다. 성능을 최적으로 유지해야 사람들이 원칙 없는 최적화로 코드를 망치려는 유혹에 빠지지 않는다. 클린 코드는 한 가지를 제대로 한다.

그래디 부치(OOAD with Application 저자) : 클린 코드는 단순하고 직접적이다. 클린코드는 잘 쓴 문장처럼 읽힌다. 클린 코드는 결코 설계자의 의도를 숨기지 않는다. 오히려 명쾌한 추상화와 단순한 제어문으로 가득하다.

데이브 토마스 (이클립스 전략의 대부) : 클린 코드는 작성자가 아닌 사람도 읽기 쉽고 고치기 쉽다. 의미있는 이름이다. 특정한 목적을 달성하는 방법은 하나만 제공한다. 의존성은 최소이며 각 의존성을 명확히 정의한다. API는 명확하며 최소로 줄였다. 때로는 정보 전부를 코드만으로 명확하게 드러내기 어려우므로 언어에 따라 문학적 표현이 필요하다.

존 제프리 (Exterme Programming Installed의 저자) : 클린 코드는 중복이 없다. 표현성이 뛰어나다. 작게 추상화되어 있다.

마틴파울러 : 컴퓨터가 이해하는 코드는 어느 바보나 다 짤 수 있다. 훌륭한 프로그래머는 사람이 이해할 수 있는 코드를 짠다.

사람이 이해하는 코드란?

  • 가독성이 뛰어나다.
  • 간단하고 작다.
  • 의존성을 최대한 줄였다.
  • 의도와 목적이 명확한 코드.
  • 타인에 의해 변경이 용이한 코드
  • 중복이 없는 코드
  • 개체(Class, Method)가 한가지 작업만 수행하는 코드

일반적인 SW 개발은 기존 코드에서 시작 ( 기존코드에서 발전 )

  • 따라서, 코드를 Write 하는 시간보다 Read 하는 시간이 더 많다.
  • SW 가 3년정도 지나면, Feature 가 향상, 수정 등으로 SW 가 진화됨. 이런 과정에서 코드가 변질되기 시작하고, 최초 의한 설계에서부터 멀어지기 시작함.
  • 그러나 현실은 BaseCode 에 필요한 작업을 추가할뿐, BaseCode 자체를 개선하는 활동을 하지 않으면서 코드는 Dirty Code 가 되고, SW 복잡도향상을 가속화시킨다.
  • 이런경우 SW 노후화가 되고, SW 노후화는 품질을 급격하게 떨굼.

어떻게 해야 사람이 이해하는 코드를 보다 쉽게 작성할 수 있을까?

  • 이름짓기 : 변수, 클래스, 인수, 상수, 패키지, 파일, 디렉토리 등 이름을 명확하게 지어야 한다.
  • 의도를 분명히 하라 : 불필요한 주석을 제거하라. (주석이 필요없도록 코드를 작성하라.)
  • 보통 주석은 코드가 무엇을 하는가? 로 작성하는데, 여기서 문제는 코드가 수정되어도 주석이 수정되지 않는 경우가 많고, 이경우 오히려 주석이 코드해석을 방해하는 경우가 생긴다.
  • 그릇된 정보를 피하라 : 길고 흡사한 이름은 피하라.
  • 함수명이 길지만 몇글자 다른경우. : IDE 자동완성에서 실수의 여지가 있음.
  • 문맥에 맞는 단어를 사용하라.
  • 이해하는데 시간을 소모하지 않기 위해 일관성 있는 단어를 사용하라.
  • 함수를 작게 만들어라
  • 함수 자체는 작게 만들기 위해 이름을 명확하게 지었지만, 함수 내의 코드가 상세하게 작성되면서 추상화 레벨이 확 낮아지면서 작성되는 경우가 있다.
  • 한가지만 하도록 만들어라
  • 하나의 함수 내에 작성된 코드가 추상화 수준이 하나인 단계만 수행하라.
  • 인수를 적게 하라.
  • 사람은 숫자 5까지 직관적으로 파악이 가능한데, 3개 이상의 인수를 직관적으로 파악할 수 없다. ( type : name, type : name, type: name) type 과 name 의 합이 총 6개가 되면서 직관적인 파악이 어렵다. 즉, 직관적으로 인지하기 위해 3개 이상의 인수는 피하라.
  • 중복하지 마라.
  • 변경 시 여러 부분을 손대야 한다. 오류가 발생할 확률이 높아지면서 수정이 어려워진다.

ch02. UML 기초

UML (Unified Modeling Language) : 소프트 웨어 개발 과정에서 산출되는 산출물을 명세화, 시각화 문서화 하기 위한 표준 모델링 언어

방법론과 UML 은 다르다.

방법론은 어떠한 작업을 할 때 이러저러한 절차를 가지고 작업을 하면 된다. 라는 것이면,

UML 은 어떤 방법론을 적용하더라도, UML 정의에 의거하여 동일한 결과물이 나온다.

UML 구성요소

  1. Things (사물)
  2. Relation (관계)
  3. Diagram (다이어그램)

Things(사물)

ClassName + Attribute + Operation() 으로 이루어짐.

ClassName : 클래스나 객체가 가져하는 이름

Attribute : 클래스나 객체가 가져야하는 정보 (값, 변수)

Operation : 클래스의 인스턴스인 객체의 행위를 나타냄

  • : Public
  • : Private

: Protected,

<<>> : Sterotype == Memo (우측상단이 접혀있는 사각형)

일반화 : 상속의 개념

실체화 : 인터페이스

연관 : 관계가 있음.

방향성을 가진 직접연관 : 의존성 - A에서 경우에 따라 B가 메모리에 탑재 - 약결합

집합연관 : A 가 메모리에 탑재된 이후에 B가 메모리에 탑재 ( new ) - 중결합

복합연관 : A와 B의 LifeCycle 이 동일함 ( Constructor ) - 강결합

Relationship(관계)

public class Shape {
		private Origin origin;
		protected void move(){}
		public void resize(){}
		public void display(){}
}

public class Circle extends Shape {
		private float radius;
}

public class Polygon extends Shape {
		private List points;
		@Override
		public void display() {}
}
  • 위 코드를 UML 로 표현한 경우
public class User {

		public Scedule makeSchedule() {
				return new Schdule();
		}

		public String getScheduleDate(Schedule schdlue) {
				String date = schedule.getDate();
				return date;
		}
}
  • 위 코드를 UML 로 표현한 경우
public interface OutputInterface{}

public class Monitor implements OutputInterface {
		...
}
  • 위 코드를 UML 로 표현한 경우
public class Teacher {
		private List<Student> students;
}
  • 위 코드를 UML 로 표현한 경우
public class Laptop {
		private Mouse wheelMouse;
}
  • 위 코드를 UML 로 표현한 경우
public class Window {
		private Frame mainFrame;
		public Window() {
			mainFrame = new Frame();
		}
}
  • 위 코드를 UML 로 표현한 경우

Diagram(다이어그램)

UML 을 정밀하게 하는 것은 프로그래밍하는 행위가 그 비용이 발생하기 때문에 전부 쓰진 않고 자주 쓰이는 것들이 있다.

  1. UseCase Diagram : 행위
  2. Class Diagram : 구조적
  3. Activity Diagram : 행위
  4. Sequence Diagram : 행위
  5. Component Diagram : 행위
  6. Deployment Diagram : 구조적

ch03~04. OOAD

객체지향 : 현실세계를 객체와 그들간의 상호관계로 이해

  • 즉, 코드를 작성할때, 그것을 명령어들의 모음으로 보는 것이 아니라 현실세계를 객체로 이해한것처럼 코드를 객체로 이해하여 작성

장점

  • 재사용성 향상
  • 생산성 향상
  • 확장 및 유지보수성 향상 : 변경은 어렵고 확장은 쉬워야 한다.

객체지향 구성요소

객체

  • 실세계에 존재하는 것 : 트럭, 자동차 //누구나 찾을 수 있다.
  • 개념으로 존재하는 것 : 공정, 주문 //도메인 지식이 있어야 한다.
  • 소프트웨어 세계에만 나타는 것 : 연결리스트, 변수 //소프트웨어 지식이 있어야 한다.

클래스

  • 동일 범주의 객체들의 특성(상태, 행위)을 정의
  • 객체들의 추상화된 형태
    • 추상화란, 복잡한 자료,모듈,시스템 등으로부터 핵심적인 개념 또는 기능을 간추려 내는 것을 말한다.

관계

  • 객체와 객체, 클래스와 클래스의 관계


객체지향 특성

캡슐화

  • 외부 호출자로부터 내부의 동작이나 데이터를 숨기는 것 (가시성)

상속

  • 부모클래스에 기능을 추가 또는 변경(확장) 하여 새로운 자식 클래스를 생성하는 것
  • 여러 클래스에 중복 또는 비슷한 코드가 존재하면 상속으로 해결할 가능성이 있다.

다형성

  • 동일한 이름의 오퍼레이션이 그 오퍼레이션이 정의된 클래스에 따라 각기 다른 행동을 수행해야 함
  • draw() 는 모두 동일하게 사용되지만, 실제 동작하는 구현은 서로 다르게 가능하다
  • 오버로딩(같은이름, 서로 다른 파라미터)과 오버라이딩(재정의)으로 해결할 수 있다.

4+1 View : 객체지향 설계를 위하여 알아야 함.

  • Logical View : 아키텍처가 어떤것인지 등, 기능에 초점을 두고 있음
  • Process View : 시스템이 동작할때 어느정도의 퍼포먼스를 두고있는지에 초점을 두고 있음.
  • Implementation View : 소프트웨어를 매니지먼트 할때 사용
  • Deployment View : 시스템 토폴로지(네트워크 요소들을 물리적으로 연결해둔 것)에 초점을 두고 있음.
    • Use-Case View : 시스템이 사용자에게 제공할 기능에 초점을 두고 있음. (요구사항)

순서는 Use-Case View → Logical View → Process or Implementation View → Deployment View

Use-Case View

  • 시스템이 사용자에게 제공할 기능을 상호작용을 중심으로 봄
  • 시스템 전체의 요구사항을 표현(단, 비기능 요구사항은 제외)
  • UML Diagram : Usecase diagram, Activity diagram, + class diagram(필요시)

Use Case Modeling

  • 시스템에 요구되는 행위를 파악하여 표현하기 위한 방법
  • 사용자 관점의 시스템 행위를 의사소통하기 위한 방법
  • 시스템과 상호작용하는 외부 사용자 또는 시스템을 식별함
  • 시스템의 범위 표현
  • 프로젝트 계획의 도구

Use Case Model 작성 순서

  1. Actor 를 식별하고 설명 기술
    1. <Actor를 찾기 위한 질문>
      1. 누가 시스템을 사용하는가
      2. 누가 시스템으로부터 정보를 취득하는가
      3. 누가 시스템에 정보를 제공하는가
      4. 누가 시스템을 운용하는가
      5. 시스템과 연동되는 외부 시스템이 있는가
  2. Use Case 를 식별하고 설명 기술
    1. <Use Case 찾기 위한 질문 : 각 Actor 에 대해 다음 질문>
      1. 시스템이 수행하기를 바라는 기본 작업은 무엇인가
      2. Actor가 시스템의 데이터를 등록/수정/삭제 하는가? 왜?
      3. Actor는 외부변경을 시스템에 알릴 필요가 있는가?
      4. Actor는 시스템 내에서 발생된 사건에 대해 알 필요가 있는가?
      5. Actor는 시스템을 구동시키거나 종료시키는가?
      <비즈니스프로세스에서 식별>
  3. Use Case 명세 작성 (전체 Use Case의 우선순위를 파악하고, 우선순위별 명세 작성)
  4. Use Case 구조화 (Include/Extend 관계 파악)

Use Case 작성시 발생하는 흔한 실수

  • 사용과 관련된 시나리오를 쓰기 보다는 기능적 요구사항을 표현하려 한다.
  • 액터와 시스템의 상호작용에 초점을 두지 않는다.
  • 시스템 반응은 무시한 채, 사용자의 요구만 기술한다.
  • 대안 흐름을 생략한다.
  • Usecase 내부 처리 방법에 대해 언급한다.

Use Case View 에서 사용하는 Activity diagram 예시

 

Logical View

  • 시스템이 사용자에게 제공할 기능과 핵심적인 아키텍처 요소를 표현
  • 3 Level 정도로 class / package 가 혼용되어 나타날 수 있다.
  • 정적인 구조(Structure)를 표현할때는 Analysis Class 의 구조를 분석하며 Class Diagram 을 사용하여 도식한다.
  • 동적인 구조(Behavior)를 표현할 때는 Analysis Class 의 행위를 분석하며 Sequence Diagram or Collaboration Diagram 을 사용하여 도식한다.
  • 간단하게, Logical View 는 Use-Case View 로부터 시작되어 정제된 class diagram 이 나오는 것
  • UML Diagram : Class diagram, Package diagram

Process View

  • 시스템의 수행흐름을 중심흐름을 중심으로 보는 관점
  • 시스템에서 작업을 수행할 때 어떤 동작을 하는지, 어떻게 통신하는지를 표현
  • UML Diagram : Sequence diagram, Communication, Activity, Composite Structure

Implementation View

  • 소프트웨어를 어떤 단위로 개발하고, 테스트하고, 패키징할 것인가에 관심
  • UML Diagram : Package diagram, Component diagram, + class diagram(필요시)

Deployment View

  • 토폴리지(네트워크 요소들을 물리적으로 연결해둔 것)에 초점을 두고 있음
  • 시스템 엔지니어의 관심과 유사
  • UML Diagram : Deployment diagram+ class diagram(필요시)

ch05. 좋은설계

좋은설계란,

  • 사용자의 기능 요구사항을 달성할 수 있는 설계
  • 사용자의 비기능 요구사항(가용성, 성능)을 달성할 수 있는 설계
  • 변경에 쉽게 대응할 수 있는 설계

위 세가지가 모두 만족되는 것이 좋은 설계이다.

그러나, 우리는 “사용자의 기능 요구사항을 달성할 수 있는 설계 + 변경에 쉽게 대응할 수 있는 설계" 정도만을 고민한다.

ch06. SOLID

설계의 원칙 : 변경되는 것과 변경되지 않는 것을 구별하라.

  • 소프트웨어는 자주 변경되는 상황에 있다.
  • 변경되는 것을 어떻게 알지? 1. git 내역, 2.구체적으로 작성된 것은 잘 바뀔 수 있다.

SDP : 보다 안정된 쪽으로 의존하라.

  • 안정성 지표 ( I 값 구하기 )
    • Fan-in : 컴포넌트 내부의 클래스에 의존하는 컴포넌트 외부의 클래스 개수
    • Fan-out : 컴포넌트 외부의 클래스에 의존하는 컴포넌트 내부의 클래스 개수
    • I(불안정성) = Fan-out / (Fan-in + Fan-out)
    • I = 0 : 가장 안정된 컴포넌트, I = 1 : 가장 불안정한 컴포넌트Ca = 2 / (2+0) = 1Cc = 1 / (3+1) = 0.25

  • SDP 를 위배한 경우에 해결하는 방법 : 위배되는 부분을 인터페이스로 분리하여 재배치

SAP : 안정된 추상화 원칙 (컴포넌트는 안정된 정도만큼만 추상화되어야 한다.)

  • 추상화정도 측정하기 (A 값 구하기)
    • Nc : 컴포넌트의 클래스 개수
    • Na : 컴포넌트의 추상 클래스와 인터페이스 개수
    • A(추상화정도) = Na / Nc
    • A = 0 : 추상화 수준이 낮다 , A = 1 : 추상화 수준이 높다

SDP 의 I 와 SAP 의 A 로 주계열 판단하기

[0,0] : 너무 구체적으로 작성되어있지만, 너무 안정적이므로 변경이 어렵다.

[1,1] : 너무 추상적이지만, 의존하는게 없어서 쓸모가 없다.

SRP(Single Responsibility Principle : 단일 책임 원칙

  • 모듈이 단 하나의 일만 해야 한다.
  • 단일 모듈을 변경의 이유가 오직 하나뿐이어야 한다.
  • SRP 를 통해 클래스의 응집력(유사한 역할)을 높이고, 다른 클래스와의 커플링(의존성)을 줄이는 목적으로 한다.
  • SRP TEST(정확) : CK Metric 에 적용하여 LCOM 으로 측정하면, 수치가 높을수록 응집력이 높다.
  • SRP TEST(불정확) : Class() 이 스스로 Func() 한다. (시대에 따라 다르게 결정날 수 있음)

OCP(Open Close Principle) : 클래스는 확장에 열려있고, 수정에는 닫혀 있어야 한다.

  • Class 를 .class 로 배포하여 잠금
  • 상속을 받아서 Override 하는 형태로 확장을 열어둠
  • [추천] A 라는 잠긴 Class 를 상속받은 B Class 를 만들고, Class 를 상속받은 new Feature Class 만들기
  • [비추천] A 라는 잠긴 Class 와 새로만든 B Interface 를 다중상속 받는 new Feature Class 만들기

LSP(Liskov Substitution Principle) : 하위 Type 은 상위 Type 으로 교체가 가능해야 한다.

  • 상속계층을 잡을 때 행위(Behavior) 를 기반으로 공통적인 것을 찾아야 함.

 

ISP : 클래스의 Client 클래스는 사용하지 않는 Interface 에 종속되지 않아야 한다.

DIP(Dependency Inversion Principle) : 개념(추상)적인 것은 상세한(구체적) 것에 의존하면 안된다.

  • 바뀌지 않는 것은 바뀌는 것에 의존하면 안된다.
  • 순환이 발생할 때, DIP 를 활용하여 순환을 끊을 수 있다.
    • 순환은 단위테스트도 어렵고, 릴리스도 어려우므로 끊어야 한다.
    • 순환은 컴포넌트를 어떤 순서로 빌드해야 올바른지 파악하기 상당히 힘들다.

ch07~09. 리팩토링

명사형 정의 : 소프트웨어를 보다 쉽게 이해할 수 있고, 적은 비용으로 수정할 수 있도록 겉으로 보이는 동작의 변화 없이 내부 구조를 변경하는 것

동사형 정의 : 겉으로 보이는 동작의 변화 없이 소프트웨어의 구조를 바꾼다.

구조를 왜 바꾸어야 하는가?

  • 소프트웨어는 만들어진 후 3년정도가 지나면 노우화가 된다.
    • 버그가 나오거나, Feature가 변경됨
    • 새로운 Feature 를 추가하기 위하여 구조를 바꿔야 한다.
  • 담당자가 자주 바뀌면 코드 노우화는 급속도로 심해진다.

이처럼 코드 노우화가 이루어지면, 새로운 Feature 를 추가하거나, 유지보수 하는 비용이 너무 많이 발생하기 때문에 구조를 변경(클린코드) 하여 코드품질을 올려야 한다.

리엔지니어링 vs 리팩토링

  • 리엔지니어링은 코드를 전반적으로 모두 뜯어고치는 행위 (이때, 새로운 Feature 도 추가)
  • 리팩토링은 부분적으로 매일 반복적으로 개선하는 것 (동작의 변화가 없다 = 새로운 Feature 추가 불가)

리엔지니어링을 하기 위해선 리버스 엔지니어 방식으로 진행해야 하는데,

개념 → 설계(Model) → 구현(Source) → 실행파일(Binary)

로 진행되는 일반적인 Forward 방식을 역순으로 진행하는 것이다.

따라서 리엔지니어링은 시간이 오래걸리며, 1년에 1번~2번 하면 많이 하는 것이다.

그러나 리팩토링은 “매일" 할 수 있다

리팩토링은 언제 해야하는가?

  • 구린내(Bad Smell)가 나면 리팩토링을 한다.
    • 주석 [쉽게 측정 가능] - DBC(모듈이 딱 맡은 역할만 하도록 작성)
    • 긴 메소드 [쉽게 측정 가능] - 함수가 너무 길면안됨. 레벨 맞춰서 쪼갤것
    • 거대한 클래스 [쉽게 측정 가능] - 의도한 기능 외에 여러 기능을 클래스가 포함해서 그런거. 쪼갤것
    • 긴 매개변수 리스트 [쉽게 측정 가능] - 3개 이상 금지 (너무길면 걍 객체로 넘겨라)
    • 이름짓기 [쉽게 측정 불가능] - 2주전 코드 읽혀지면 클린코드. 아니면 고쳐야함
      • 의도를 분명하게
      • 그릇된 정보 피해서
      • 의미 있게
      • 발음하기 쉽게
      • 검색하기 쉽게
      • 인코딩 (약자) 금지
      • 기억력 자랑 ㄴㄴ
    • 중복 [PMD 의 CPD 로 어느정도는 측정 가능]
    • 죽은코드(미사용코드) 제거
    • 드모르간 법칙으로 부정조건 제거
    • 복잡한 switch - case 면, 다형성을 사용해서 정의
  •  

'BackUp (관리중지) > CS 학습' 카테고리의 다른 글

DI (Dependency Injection )  (0) 2021.04.29
REST API  (0) 2021.04.28
Java GC  (0) 2021.04.28
GC ( Garbage Collection )  (0) 2021.04.28
동시성 이슈  (0) 2021.04.27

+ Recent posts