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


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

<문제 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

의존성 주입(DI) 는 안드로이드 뿐만 아니라, 객체지향 프로그래밍을 할 때 한번쯤 듣는 단어입니다.

의존성이란

하나의 객체가 동작함에 있어서 다른 객체를 반드시 필요로 하는 경우를 말합니다.

A 클래스에서 B클래스를 반드시 필요로 한다. 즉, B클래스가 없으면 A클래스는 동작하지 않는다.

이런 경우 A클래스는 B클래스에 의존하고 있다. 라고 보면 될 것 같습니다.

의존성 주입(DI) 란,

의존성을 갖는 객체가 있다면, 그 객체가 의존하고 있는 객체를 외부에서 주입해주는 것입니다.

 

DI 를 사용하는 이유

지금부터 상황을 토대로 DI 를 사용하는 이유에 대해서 시작하겠습니다.

이 상황은 DI 설명을 위해 지어낸 상황입니다. (현실성에서 굉장히 떨어질 수 있습니다.)

 

Chapter1. 처음엔 1개 뿐이였어

요청 : 안드로이드 앱 개발자를 구하고 있습니다. 이 개발자는 AndroidStudio 를 사용해서 앱을 개발해야 합니다.
답변 : 안드로이드 개발자 클래스를 전달드리겠습니다. 필요하실 때마다 해당 클래스를 생성하시면, 안드로이드 앱 개발자를 얻게 되실겁니다.
class AndroidDeveloper {
  AndroidStudio androidStudio;
  
  AndroidDeveloper() {
  	androidStudio = new AndroidStudio();
  }

  void createAppAOS() {
  	androidStudio.createApp();
  }
}

Chapter2. 비슷하지만, 새로 만들면 돼

요청 : 저번에 안드로이드 개발자클래스를 잘 쓰고있습니다. 근데 이번엔 iOS 앱 개발자를 구하고 있습니다. iOS 앱 개발자는 Swift 를 사용해야 합니다.
답변 : iOS 개발자 클래스를 전달드리겠습니다... 역시 필요하실 때마다 해당 클래스를 생성하시면 됩니다.
class iOSDeveloper {
  xCode xCode;
  
  iOSDeveloper() {
  	xCode = new xCode();
  }

  void createAppiOS() {
  	xCode.createApp();
  }
}

Chapter3. 안좋은 예감이 들어

요청 : iOS 개발자가 그만두었습니다. 이참에 그냥 안드로이드 개발자가 iOS 개발도 할 수 있게 만들어주면 안되나요?
답변 : 네 알겠습니다... ㅂㄷㅂㄷ 수정해드릴게요
class AndroidDeveloper {
  AndroidStudio androidStudio;
  xCode xCode;
  
  AndroidDeveloper() {
  	androidStudio = new AndroidStudio();
  	xCode = new xCode();
  }

  void createAppAOS() {
  	androidStudio.createApp();
  }

  void createAppiOS() {
  	xCode.createApp();
  }
}

Chapter4. 이럴줄 알았어

질문 : 이거 혹시 그전 iOS 개발자 클래스도 그대로 쓸 수 있는거 맞죠?
답변 : 네 맞습니다...
요청 : 그냥 안드로이드 개발자, iOS 개발자, 두개 다 가능한 개발자 세개로 만들어주세요.
답변: 예예...
class AndroidDeveloper {
  AndroidStudio androidStudio;
  AndroidDeveloper() {
  	androidStudio = new AndroidStudio();
  }

  void createAppAOS() {
  	androidStudio.createApp();
  }
}

class iOSDeveloper {
  xCode xCode;
  iOSDeveloper() {
  	xCode = new xCode();
  }

  void createApp() {
  	xCode.createApp();
  }
}

class AppDeveloper {
  AndroidStudio androidStudio;
  xCode xCode;
  AppDeveloper() {
  	androidStudio = new AndroidStudio();
  	xCode = new xCode();
  }

  void createAppAOS() {
  	androidStudio.createApp();
  }

  void createAppiOS() {
  	xCode.createApp();
  }
}

Chapter5. 그렇게 1개에서 100개까지 늘어났지

요청 : 이번엔 Spring..
요청 : 이번엔 ~~~~~

...

요청 : 이번엔 풀스택~~

 

Chapter6. 절망의 순간

요청 : 보니까 개발자가 사용하는 툴을 생성하더라구요? 그 툴에서 사용하는 언어도 선택할 수 있게 해주세요.
답변 : 그러니까.. 이런식으로요?
class AndroidStudio {
Language language;
  AndroidStudio(Language language) {
  	this.language = language;
  }
  void createApp() {
	//...
  }
}
요청 : 어 맞아요. 그런데 어라... 이전에 제공해주신 코드들이 모두 에러가 나요

 

Chapter7. 회고의 순간

아.... 이거 사용하고 있는 코드들 다 찾아서 다 고쳐줘야하네...

 

이게 다 개발자 생성하는 클래스마다 싹다 툴 클래스에 의존하고있어서그래!!

애초에  개발자 클래스를 생성할 때 툴을 전달받게 했으면, 개발자 클래스를 생성하는 부분에서만 고치면 편할텐데 하............

 

다음에는 이런 부분은 진짜 고려해서 작성해야겠다!!

 

 

Chapter8. 이런 상황을 겪은 나에게 필요한 건 뭐다!?

"Constructor injection" , 바로 생성자 주입이라는 것이다!!!


 

Chapter9. 근데.. 만약에 생성자를 직접 안넣고  그냥 set 으로 전달받으면 어쩌지?

class AndroidDeveloper {
  AndroidStudio androidStudio;
  AndroidDeveloper(String lang) {
  	androidStudio = new AndroidStudio();
  	androidStudio.setLanguage(lang);
  }
  void createAppAOS() {
  	androidStudio.createApp();
  }
}

 

이렇게 AndroidStudio 객체가 언어를 필요로 하는데, 이 언어를 setLanguage 처럼 직접 넣어주는 경우를

"Filed injection" , 바로 필드 주입 이라고 한다.

 

 

 


의존성 주입의 장점

  • Unit Test가 용이해진다.
  • 코드의 재활용성이 높아진다.
  • 객체 간의 의존성(종속성)을 줄이거나 없앨 수 있다.
  • 객체 간의 결합를 낮추기 때문에 유연하게 코드를 작성하며, 유지보수가 쉬워진다.
  • 보일러 플레이크 코드가 줄어든다.

위 예시에서는 의존성 주입의 장점들을 모두 담아내지 못했습니다. (ex. TestCode , 재활용성 등 )

결국 클린하게 코드를 작성하기 위하여, 객체지향을 객체지향답게 작성하기 위하여

 

"의존성 주입" 은 선택이 아니라, 필수입니다.

 

아마 DI 를 도와주는 라이브러리를 사용하지 않아서, DI 를 모른다. 적용해본적 없다. 라고 생각하시는 분들이 계실지도 모르겠습니다.

그러나, DI 는 꼭 라이브러리가 있어야만 하는 것이 아닙니다. 여러분은 알게 모르게 사용하셨을 수도 있습니다. ( 의존성 주입만을 담당하는 Class 를 따로 둔다거나 하는 것까지 아니더라도)

겁먹지 말고, 클린코드를 위하여 편안한 유지보수로 미래의 "자신"이 더 행복하기 위하여 이제는 시작해봅시다.

 

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

개발자 기초지식 [ 내용요약 ]  (0) 2022.11.12
REST API  (0) 2021.04.28
Java GC  (0) 2021.04.28
GC ( Garbage Collection )  (0) 2021.04.28
동시성 이슈  (0) 2021.04.27

1. REST : HTTP 의 장점을 활용할 수 있도록 만든 아키텍처

2. REST API : REST 아키텍처에 맞게 설계한 API

3. RESTful API : API 가 REST의 제약을 잘 지킬수록 Restful 하다고 말합니다.

 

REST

REST (REpresentational State Transfer) 은, Roy Fielding 박사학위 논문에서 최초로 소개되었습니다.

HTTP 의 주요 저자 중 한 사람으로 그 당시에 HTTP 설계의 우수성에 비해 제대로 사용되어지지 못하는 모습에 장점을 최대한 활용할 수 있는 아키텍처로 REST 를 발표하였습니다.

 

REST 구성

ReSource

- 자원의 위치를 정의합니다.

- Client 는 URI 를 이용하여 자원에 접근할 주소를 최종 호출 단위까지 명확하게 특정합니다.

 

Verb

- 자원에 대한 행위를 정의합니다 (HTTP Method)

- Client 는 HTTP Method 를 이용하여 지정한 자원에 대한 조작을 요청합니다.

 

Representation

- 자원에 대한 행위의 내요을 정의합니다.

- Client가 Server에 자원에 대한 조작을 요청할 때, 조작에 필요한 데이터를 메시지로 표현하여 전송합니다.

 

HTTP Method ( REST API 에서 주로 사용되는 HTTP Method )

HTTP Method Type 설명 페이로드
PUT Replace 리소스 전체 교체  O
POST Create 리소스 생성 O
PATCH Modify 리소스 부분 교체 O
GET Read 리소스 조회 X
DELETE Delete 리소스 삭제 X

* 페이로드 : 여기서는 Message를 의미합니. 흔히, Body 에 Json Type 으로 Data 를 넘겨주는데 이같은 경우가 해당됩니다.

 

REST 데이터 포맷

흔히, 데이터를 JSON 형식으로 전송하고 JSON 형식으로 받기 때문에 JSON 만가능한 것으로 착각하는 경우가 있습니다.

 

그러나, JSON, HTML, XML, JavaScript, TEXT 등 다양한 포맷을 지원합니다.

 

 

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

개발자 기초지식 [ 내용요약 ]  (0) 2022.11.12
DI (Dependency Injection )  (0) 2021.04.29
Java GC  (0) 2021.04.28
GC ( Garbage Collection )  (0) 2021.04.28
동시성 이슈  (0) 2021.04.27

앞선 글에서, GC 는 프로그램 동적으로 할당했던 메모리 영역 중에서 필요없게 된 영역 해제 하는 메모리 관리 기법 이라고 하였습니다.

 

또한 Java에서 GC 는 JVM 의 가비지컬렉터 가 동작한다고 하였는데요,

 

이번 글에서는 Java GC 에 대해서 조금 더 알아보려고 합니다.

 

* Heap영역 : 자바에서 객체를 생성하면 Heap 영역에 저장

Minor GC & Major GC

JVM 은 Heap영역을 설계할 때 2가지 전제조건으로 설계 되었습니다.

1. 대부분의 객체가 금방 접근 불가능한 상태가 된다.

2. 오래된 객체에서 새로운 객체로의 참조는 드물게 존재한다.

 

이는 대부분의 객체가 일회성인 경우가 많고, 메모리에 오래 남아있는 경우가 드물다는 것을 의미합니다.

 

이에 따라 Young영역 , Old 영역 으로 구분을 하여 설계하였습니다.

 

Young 영역 : 새로운 객체의 영역

- 새로운 객체는 이곳에 할당되며, 대부분의 객체가 일회성인 만큼 대부분의 객체는 Young 영역에 오래 남아있지 않고 사라집니다.

- Young 영역을 관리하는 GC 를 Minor GC 라고 합니다.

또한, Young 영역의 구조는 아래와 같이 이루어져 있습니다.

- Eden 영역 : 새로 생성된 객체가 할당되는 영역

Eden 영역이 가득찰때마다, MinorGC 실행

- Survivor1 영역 : Eden 영역에서 1번 이상 살아남은 경우 할당되는 영역

- Survivor2 영역 : Survivor1 영역이 가득찬 경우 Survivor1 영역에서 살아남는 객체가 할당되는 영역

Survivor2 영역에서 계속 해서 살아남으면, 해당영역의 객체들은 Old 영역으로 복사

 

Old 영역 : 오래된 객체의 영역

- 새로운 객체가 Young 영역에서 사라질 때, Minor GC 를 거쳤지만, 해제되지 않고 살아남은 객체들이 이곳으로 복사됩니다.

- Old 영역에서도 언젠가는 사라져야하는데 이러한 Old 영역을 관리하는 GC 를 Major GC 라고 합니다.

- Old 영역은 Young 영역보다 크며, MinorGC 에 비해 MajorGC는 10배 이상의 시간을 필요로 합니다.

- 이는 MajorGC 가 자주 호출된다면, 성능에 영향을 끼칩니다.

- 그에 따라 MajorGC 는 Old 영역의 메모리가 부족해질 때 호출됩니다.

Card Table 

위에서 오래된 객체에서 새로운 객체로의 참조는 드물게 존재한다고 하였습니다.

즉, 오래된 객체가 새로운 객체를 참조할 수도 있다. 라는 것을 의미합니다.

 

그렇다면,

Minor GC 는 Young 영역에서 해제되어야하는 대상을 찾아야할 때, Old 영역도 확인해야 겠네? 라는 결론이 나오게 됩니다.

하지만 문제가 하나 있습니다. Old 영역에 있는 객체가 Young 영역에 있는 객체를 참조하는 경우는 드물게 발생한다 에서

Old 영역중에 0개 or 1개 의 객체만 Young 영역의 객체를 참조할 수 있다는 것입니다.

 

n 개의 객체를 제거대상인지 식별하기 위하여 m개의 객체를 검사한다면 n*m 번의 검사가 실행됩니다.

이러한 문제를 해결하기 위하여 Card Table 이 등장합니다.

 

Card Table : Old 영역의 객체가 Young 영역의 객체를 참조하는 경우에 그에 대한 정보를 저장합니다.

- Minor GC 가 동작할때 Young 영역에 있는 객체들은 Old 영역에 있는 객체와의 n*m 번의 검사가 아니라,

Card Table 에 있는 객체를 확인하여 Young 영역의 객체중 GC 의 대상에서 벗어나야 하는 객체를 식별합니다.

 

기본적인 GC 공통 동작 : Stop The World & Mark And Sweep

MinorGC 와 MajorGC 는 세부적으로 동작방식이 다릅니다.

간단하게 MinorGC 에서는 CardTable 도 사용하는 로직도 필요하다는 것을 예로 들 수 있겠죠?

 

하지만 기본적으로 GC 가 실행될 때 공통적으로 동작하는 방식이 있습니다.

 

1. Stop The World

- JVM 의 가비지컬렉터가 GC 를 실행하기 위하여 GC 를 실행하는 쓰레드를 제외한 나머지 쓰레드를 모두 정지시킵니다.

GC 작업이 종료되면, 나머지 쓰레드를 다시 동작합니다.

즉, Stop The World란 JVM 이 GC 를 실행시키기 위하여 다른 쓰레드를 정지&재개하는 동작입니다.

 

2. Mark And Sweep

- GC 가 동작할 때, 사용되는 메모리와 사용도지 않는 메모리를 식별하는 Mark 작업과 사용도지 않는 메모리가 식별된 경우, 이를 해제하기 위한 Sweep 작업입니다.

 

 

 

요약

자바의 GC 는 MinorGC 와 MajorGC 로 나뉩니다.

MinorGC : Young 영역에 대한 GC, Young 영역의 구조중 Eden영역이 가득차면 실행됨. 실행속도가 빠르다.

MajorGC : Old 영역에 대한 GC, Old 영역이 가득차면 실행됨. 실행속도가 느리다.

 

* 객체의 잘못된 사용으로 GC 의 대상이 되지 못하여 메모리 누수가 일어나는 경우 Old영역에 할당되는 객체가 늘어나게 되고,

Old영역이 가득찰때마다 MajorGC 가 실행되는데, MajorGC 에서 해제되는 메모리가 적어지는 만큼 Old영역이 가득차는 경우가 빈번해집니다. 따라서 MajorGC 가 자주 호출되게 되고, 이는 곧 성능에 치명적으로 다가옵니다.

그 이후, Old영역이 가득 찾지만, 더이상 MajorGC 의 대상에 속하지 못하여 사용할 수 있는 메모리가 없어진 순간

펑~ 펑~ 펑 .....

 

메모리누수를 조심합시당 :)

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

DI (Dependency Injection )  (0) 2021.04.29
REST API  (0) 2021.04.28
GC ( Garbage Collection )  (0) 2021.04.28
동시성 이슈  (0) 2021.04.27
쓰레드(Thread) / 프로세스(Process)  (0) 2021.04.27

GC란,

Java 를 경험한 사람들은 가비지컬렉션, 가비지컬렉션 하는 말들을 못들어봤을리가 없습니다.

그러나, Java 로 개발하는 사람들에게 가비지컬렉션이 뭔가요? 라는 질문을 하면 흠칫 하는 경우가 많습니다.

알고 있는데, 설명을 하지 못하는 것은 모른다고 보는게 맞습니다.

 

오늘은 이렇게 모르면 안되는, 하지만 모르는 경우가 많은

가비지 컬렉션(GC) 에 대하서 알아보려고 합니다.

 

GC (Garbage Collection) 란,

프로그램동적으로 할당했던 메모리 영역 중에서 필요없게 된 영역해제 하는 메모리 관리 기법입니다.

 

" 더이상 사용되지 않는 메모리를 제거합니다. "
" 참조되지 않는 메모리를 해제합니다."

" 자동으로 메모리를 관리해줍니다. "

와 같은 답변은 맞는듯 아닌듯 저 말만 듣고는 이해하기 쉽지 않습니다.

 

앞으로 GC 가 무엇인가요? 에 대한 답은 

프로그램 동적으로 할당했던 메모리 영역 중에서 필요없게 된 영역 해제 하는 메모리 관리 기법입니다.

로 했으면 합니다.

 

우리가 모르고 넘어가던 핵심포인트는 GC 는 메모리 관리 기법 중 하나 라는 것입니다.


GC 는 어쩌다 탄생했나?

기본적으로 C 와 C++ 등의 언어에서 사용되지 않는 메모리 영역을 해제하기 위해 수동으로 메모리를 관리하였습니다.

Lisp 라는 언어에서 이같은 수동 메모리 관리를 단순화하기 위하여 GC 가 발명되었습니다.

역사공부는 적당히 하고.... 결국

 

"수동 메모리 관리" -> "자동 메모리 관리" 를 하기 위해 탄생했다고 보면 됩니다.

 

GC 장단점

GC 를 기본적으로 제공하는 언어나, GC를 구현한 프로그램 에서는 다음과 같은 장단점이 존재합니다.

<장점>

아래와 같은 버그를 줄이거나, 방지할 수 있습니다.

 

1. 유효하지 않은 포인터 접근

- 이미 해제된 메모리에 접근하는 경우에 발생하는 버그입니다.

만약 메모리에 다른 값이 새로 할당되었다면, 해당 메모리를 바라보던 포인트는 전혀 다른 값을 반환하게 됩니다.

 

2. 이중 해제

- 이미 해제된 메모리를 다시 해제하는 경우에 발생하는 버그입니다.

ex) free() 같이 수동으로 메모리를 해제 하는 과정에서 이미 해제된 메모리를 한번더 해제 하면서 에러를 발생시킵니다.

 

3. 메모리 누수(메모리 릭)

- 프로그램이 더이상 필요로 하지 않는 메모리 영역이 해제되지 않고 남아있는 경우를 말합니다.

메모리 누수가 반복되는 경우 사용가능한 메모리 영역이 고갈나게되고, 그 경우 프로그램은 중단됩니다.

* 메모리 누수가 아님에도 메모리 영역이 고갈나는 경우는 GC 로도 막을 수 없습니다.

 

<단점>

 

1. 해제해야 하는 메모리를 결정하는 오버헤드

- GC 는 프로그램 동적으로 할당했던 메모리 영역 중에서 필요없게 된 영역 해제 하는 메모리 관리 기법입니다.

라고 하였습니다. 그렇다면 필요없게 된 영역을 누가 알려주죠? 개발자가 직접 알려주나요? 나 여기 필요없어.

그럴거면 수동으로 메모리를 관리하는 것과 무슨 차이가....

따라서, GC가 동작할 때, 필요없게 된 영역을 결정해야 하는 로직이 동작합니다.

이때 결정하기 위한 오버헤드가 발생합니다.

 

2. 할당된 메모리가 해제되는 시점을 알 수 없다.

- 일반적으로 GC 는 개발자가 호출하지 않습니다.(자바의 경우 JVM 의 가비지컬렉터 가 이를 실행합니다.) 즉, 개발자가 "변수=null" 을 통해서 더이상 사용하지 않기 위해 참조를 해제한다고 하더라도, GC가 동작하기 전까지는 메모리 영역에서 해제되지 않는다는 겁니다.

 

안드로이드를 기점으로 생각해볼까요? Activity 가 실행되고 있을 때, 해당 Activity 가 종료되면 Activity 가 사용하던 메모리 영역은 그 즉시 해제되나요? 확신할 수 있나요?

 

GC원리를 학습하면, 해제해야하는 대상을 결정하는 로직을 보게 됩니다. 그런 로직에 따라서 결정되는데 프로그램을 개발하는 순간에 GC 가 해제해야 하는 대상을 결정하는 로직과, GC가 일어나는 타이밍들을 100% 고려하여 작성할 수 있나요?

 

저희는 GC가 언제 동작하는지 예측하는것도 어려울 뿐더러, 그 GC 가 동작하는 순간에 어떤 메모리 영역을 해제해야 대상으로 선택했는지 에 대해서 알 수 없다고 봐야합니다.

 

 

< Java GC 에 대해서 좀 더 알아보고 싶다면 >

devhyeon0312.tistory.com/17 게시글을 확인해주세요 🤗

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

REST API  (0) 2021.04.28
Java GC  (0) 2021.04.28
동시성 이슈  (0) 2021.04.27
쓰레드(Thread) / 프로세스(Process)  (0) 2021.04.27
동기(Synchronous) / 비동기(Asynchronous)  (2) 2021.04.27

동시성 이슈는 결국 "공유자원" 으로 인해 발생한다고 보면 될 것 같습니다.

 

공유자원이란

그렇다면, "공유자원" 은 무엇일까요?

정말 단어 그대로 순수하게 자원을 공유한다고 보면 될 것같습니다.

서버와 클라이언트를 본다면, DB 의 데이터가 공유자원이 될수 있고,

프로세스 간에는 IPC 통신을 하면서 여러프로세스가 동일한 자원에 접근하는 경우에 해당 자원이 공유자원이 될 수 있고,

쓰레드간에는 여러쓰레드가 프로세스 힙메모리 등에 있는 자원에 접근하는 경우에 해당 자원이 공유자원이 될 수 있습니다.

 

단, 변하지 않는 Read Only 의 자원에 대해서는 동시성 이슈가 발생하지 않습니다.

그러나, 수정이 일어나는 자원에 대해서는 굉장히 조심해야 합니다.

 

실생활 예시

친구들과 팀프로젝트를 진행중입니다. 파일을 공유하기 위해 구글드라이브 사용하고 있네요.

팀장만 파일을 등록/삭제/수정 이 가능하다면, 팀원들이 받는 파일은 항상 팀장이 마지막으로 수정한 파일일 것입니다.

그러나 모두가 등록/삭제/수정 이 가능하다면?

0.팀원A와 팀원B가 파일을 각각 다운받았습니다.

1.팀원A가 파일에 1이라고 적혀있는 것을 0으로 고쳤습니다.

2.팀원B가 파일에 1이라고 적혀있는 것을 2로 고쳤습니다.

3.팀원A는 파일을 저장하였습니다.

4.팀원B도 파일을 저장하였습니다.

 

팀원A가 고친 값은 공중으로 사라지게 됩니다.

 

또다른 예를 볼까요?

0.팀원A와 B가 파일을 각각 다운받았습니다.

1. 팀원A는 발표횟수가 0이라고 되어있는 파일을 가지고 발표를 진행합니다.

2. 발표를 끝낸 팀원A는 발표횟수를 1로 올려두고 파일을 저장합니다.

3. 팀원B는 발표횟수가 0이라고 되어있는 파일을 가지고 발표를 진행합니다.

4. 발표를 끝낸 팀원B는 발표횟수를 1로 올려두고 파일을 저장합니다.

 

어라? 발표는 분명 2번 일어났는데, 최종적으로 발표는 1번만 한것이 됩니다.

 

Android Thread 에서 발생하는 동시성 이슈

Android 를 예시로 들겠습니다. 

안드로이드 프로젝트를 처음 동작시키면, UI Thread 와 Main Thread 가 존재합니다.

UI Thread 에서는 정말 UI만 그리는 작업을 진행합니다.

Main Thread 에서는 UI를 그리는데 필요한 데이터를 처리하는 작업을 진행합니다.

 

만약 Main Thread 에서 아직 처리되지 않아서 null 인데이터를 UI Thread 가 그리려고 하면 NullPointException 이 발생할 것입니다.

UI를 그리기 위해 필요한 Data 는 "공유자원" 에 해당합니다.

 

이처럼 당장 눈에 Thread 라는 것을 정의하지 않더라도, 자신이 사용하는 프레임워크라던가, 동작의 원리등에서 발생할 수 있는 "동시성 이슈" 는 항상 고민되어야 합니다.

 

Android API 동시성 이슈

간단한 API 를 단계별로 호출하고, 결과에 따라서 UI 를 update 하는 경우에는 크게 접하지 못했을 수도 있습니다.

그러나, 한 화면에 여러개의 API 가 호출되고, 여러개의 API 결과에 따라 UI 가 update 되는 경우에는 이런 문제가 발생할 수도 있습니다.

 

예를 하나 들어보겠습니다.

 

API 요청결과는 SUCCESS, FAIL 두가지로만 분류된다고 가정하겠습니다.

SUCCESS 인 경우에는 UI update 에 필요한 데이터가 null 인 경우는 없다고 가정하겠습니다.

또한 FAIL 의 경우에는 재시도 없이 FAIL UI 를 보여준다고 가정하겠습니다.

 

한개 API

1. API 요청 및 응답대기

2. SUCCESS : 필요한 데이터로 UI Update

2. FAIL : FAIL UI 표시

 

API 결과가 무엇이든, 동작에 문제는 없습니다.

 

여러 API

1. 첫번째 API 요청 및 응답대기

2. 두번째 API 요청 및 응답대기

 

3. 첫번째 API SUCCESS : 필요한 데이터로 UI Update // 두번째 API 결과로 얻는 Data 가 null 이라서 FatalError 발생

3. 첫번째 API FAIL : FAIL UI 표시

 

4. 두번째 API SUCCESS : FAIL UI 에서 SUCCESS UI 로 변경을 시도, // 첫번째 API 결과로 얻은 Data 가 null 이라서 FatalError 발생

5. 두번째 API FAIL : FAIL UI 표시

 

운좋게(?) 두개의 API 가 모두 FAIL 인 경우에는 FAIL UI 를 표시하면 되기 때문에 의도대로 동작한다고 볼수도 있습니다.

그러나, 한개의 API 라도 SUCCESS 가 일어나는 순간 저 로직은 FatalError 가 발생합니다.

 

해결과정을 알아볼까요?

Null 이 아닌 경우에만 해당 데이터가 사용되는 UI 를 그리라는 것입니다.

그러면 최소한 FatalError 에서는 벗어나겠지요?

 

그런데.. 저게 정말 맞는 해결방법일까요?

정답은. 때에 따라서 맞을수도, 틀릴수도 있다 입니다.

 

만약 정말 보이고자 하는 의도가 NULL 인 경우에 공백 등으로 UI 표시하는 것이 목적이라면, 저것은 맞다고 할 수 있습니다.

그러나, 의도된 UI or 다음 동작을 위해서 2개의 API 의 결과가 모두 필요한 경우라면, 저것은 틀리다고 할 수 있습니다.

 

만약 2개 API 의 결과가 필요하다면,  2개 API 의 반환된 결과에 따라서 처리로직이 구현되어야 합니다.

<첫번째 방법>

1. 첫번째 API 요청 및 응답대기

2. 첫번째 API SUCCESS 로 인해 대기

3. 두번째 API 요청 및 응답대기

4. 두번째 API SUCCESS 로 인해 UI Update

또는 첫번째 API FAIL or 두번째 API FAIL 의 경우에는 추가 요청없이 FAIL UI 표시

 

의 방법이 있겠네요. 이것은 여러 API 를 "순차적" 으로 사용하여 해결하는 방법입니다.

그러나, API 가 10개라면? 20개라면? 100개라면?

연관된 API 가 늘어날수록 위와같은 방법은 코드의 길이와 예외의 상황을 더이상 기억할수 없게 만들어버립니다.

 

<두번째 방법>

그럼 다음 방법을 살펴볼까요?

0. UI update 를 판단하는 쓰레드 동작

1. 첫번째 API 요청 및 응답대기

2. 두번째 API 요청 및 응답대기

3. 세번째 API 요청 및 응답대기

 

4. API 요청 결과가 모두 SUCCESS 인 경우 : 0번의 쓰레드의 SuccessCount = 3; 으로 UI Update

4. API 요청 결과가 부분 SUCCESS 인 경우 : 0번의 쓰레드의 SuccessCount < 3 && FailCount > 0 이므로 FAIL UI 표시

 

두번째 방법은 실제 사용시 Observer 를 사용한다거나, 편리하게 Rx를 사용한 API 콜을 처리하는 등으로 나아가게 됩니다.

 

이러한 과정은 결국 "동시성 이슈" 로 인해 나온 것이며, "왜" 필요한지 알게 되셨길 바랍니다.

 

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

Java GC  (0) 2021.04.28
GC ( Garbage Collection )  (0) 2021.04.28
쓰레드(Thread) / 프로세스(Process)  (0) 2021.04.27
동기(Synchronous) / 비동기(Asynchronous)  (2) 2021.04.27
HTTP (HyperText Transfer Protocol)  (0) 2021.04.26

프로그램(Program)

특정 작업을 하기 위해 일련의 명령어 모임

응용 소프트웨어(application software) & 어플리케이션(Application)

OS 위에서 동작하는 모든 소프트웨어


프로세스(Process)

프로세스란, 컴퓨터에서 연속적으로 실행되고 있는 컴퓨터 프로그램을 의미합니다.

또한, 메모리에 올라와서 실행되고 있는 프로그램의 인스턴스 라고 할 수 있습니다.

프로세스 메모리 영역 ( 코드, 데이터, 스택, 힙 )

프로세스 구조 ( 레지스터 + 메모리영역 )

프로세스 특징

각 프로세스는 독립적이며, 서로 다른 프로세스의 자원에 사용이 불가능하다.

만약, 서로 다른 프로세스의 자원을 사용하기 위해서는 프로세스간 통신을 구현해야한다. ( IPC 통신 )

만약 프로세스간 통신을 구현했을 때, 공유자원에 대한 동시성 이슈에 대해서도 고민해야 한다.

 

 

쓰레드(Thread)

쓰레드란, 프로세스 내에서 실행되는 흐름의 단위 를 의미합니다.

일반적으로 1개의 프로그램은 1개의 쓰레드를 가지고 있지만, 1개의 프로그램환경에 따라 여러개의 쓰레드를 가질 수 있습니다.

 

쓰레드 메모리 영역 ( 스택 )

쓰레드 구조 ( 레지스터 + 메모리영역)

쓰레드 특징

각 쓰레드는 속해있는 프로세스의 Code, Data, Heap 영역에 접근할 수 있다.

따라서, 하나의 쓰레드에서 프로세스 힙영역의 자원을 수정하면, 다른 쓰레드 변경된 자원의 결과를 알 수 있다.

이는 서로 다른 쓰레드가 같은 공유자원을 서로 다른 기준으로 변경했을 때, 동시성 이슈를 발생할 수 있다.

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

GC ( Garbage Collection )  (0) 2021.04.28
동시성 이슈  (0) 2021.04.27
동기(Synchronous) / 비동기(Asynchronous)  (2) 2021.04.27
HTTP (HyperText Transfer Protocol)  (0) 2021.04.26
자료구조(Data Structure)  (0) 2021.04.26

먼저,  동기와 비동기에 들어가기에 앞서 대체 헷갈릴 수 있는 단어들의 종류를 적고 시작하겠습니다.

동기 (Synchronity) : "같은시간"에는 1개의 작업만 처리할 수 있다.

비동기 (Asynchronous) : "같은시간"에 여러개의 작업을 처리할 수 있다.

동시성 (Concurrency) : 병렬과 병행을 모두 포함하여 사용됨. (모호함)

병렬성 (Concurrency) : A작업과 B작업이 "다른시간"에 번갈아가며 동작하며, "같은시간"에 실행되는 것처럼 보이게 할 수 있음.

병행성 (Parallelism) : A작업과 B작업이 "같은시간"에 같이 실행될 수 있음.

순차적 (Sequential) : 작업이 들어오면 해당 작업이 끝날 때 까지 다른 작업을 진행할 수 없음.

 

제가 "동기와 비동기" 로 블로그를 찾아보았을 때, 이런 정의를 본적이 있습니다.

- 동기는 말 그대로 동시에 일어난다는 뜻입니다.

- 비동기는 말 그대로 동시에 일어나지 않는다를 의미합니다.

해당 글의 내용을 전부 읽었을 때, 그 설명이 틀리지는 않았다는 것을 알 수 있었습니다.

근데 뭔가 느낌이 좋지 않았습니다. 왜???? 내용을 전부 읽지 않는다면, 저 표현은 엄청난 오해(의도와 정말 극단적으로 반대되는 해석) 를 살 수 있을 것 같기 때문입니다.

 

왜 오해를 사는지 살펴보겠습니다.

 

병렬(Concurrency) 은 A 작업과 B 작업이 실행되는 것이 동시(Concurrency) 에 일어나는 것처럼 보이지만, 실제로는 순차적으로 진행됩니다. (ex.멀티쓰레드)

 

병행(Parallelism) 은 A 작업과 B작업이 실행되는 것이 동시(Concurrency) 에 일어납니다. (ex. 멀티프로세스)

 

동시성(Concurrency)은 이러한 두 상황을 통틀어서 말하는 경우가 많습니다. 

 

자, 그럼 위에 정의로 가볼까요?

 

"동기(Synchronity)는 말 그대로 동시(Concurrency)에 일어난다는 뜻입니다."

- 동기는 마치 병행을 말하는 것 같네요. 포괄적으로 본다해도, 병렬을 말하는 것 같아요.

 

"비동기(Asynchronous)는 말 그대로 동시(Concurrency)에 일어나지 않는다를 의미합니다"

- 위에서 말한 동시가 병행을 말한것이라면, 이건 병렬을 말한거겠네요.  아니면, 위에서 말한 동시가 병행,병렬을 말한거면 이 친구는 그 어디에도 속할 수 없네요. 음.. 순차적 (Sequential) 이 된다는 것일까요?

 

어떤 해석이든 둘다 문제가 있습니다.

좌측은 동기(Synchronity) 의 진행과정을 표현했으며, 우측은 비동기(Asynchronous) 의 진행과정을 표현했습니다.

(빨,노,초 작업은 각각 필요한 데이터 수신을 의미한다고 하겠습니다.)

 

아~ 그럼 동기는 순차(Sequential) 이고, 비동기는 동시(Concurrency) 인가요?

아닙니다. 다른 그림을 또 볼까요?

 

 

 

좌측은 동기(Synchronity) 의 진행과정을 표현했으며, 우측은 비동기(Asynchronous) 의 진행과정을 표현했습니다.

(빨강은 UI 그리기, 노랑과 초록은 필요한 데이터 수신을 의미한다고 하겠습니다.)

 

정리하자면,

 

1. 동기(Synchronity) 는 작업중에 다른 작업이 들어오면 해당 작업을 멈추고, 다른 작업을 진행합니다. 다른 작업을 끝낸 후에 하지못한 작업을 마무리하죠. 즉, 동시간대에는 1개의 Task 만을 수행합니다.

ex. UI 를 그리는 작업을 하는 중에 데이터를 받아오는 작업을 진행하면, UI 그리는 작업을 중지하고, 데이터 받아오는 작업을 진행한 후에 UI 그리는 작업을 다시 진행합니다.

로딩화면(빙글빙글) -> 로딩화면정지(stop) -> 데이터수신

 

2. 비동기(Asynchronous) 는 작업중에 다른 작업이 들어오면 해당 작업을 멈추는 것이아니라, 동시(Concurrency)적으로 진행합니다.

멀티프로세스에서 비동기를 진행하면 병행이 될것이고, 단일프로세스에서 비동기를 진행하면 병렬로 처리할 것입니다. 즉, 동시간대에 여러개의 Task 를 수행합니다.

ex. UI를 그리는 작업을 하는 중에 데이터를 받아오는 작업을 시작하면, 동시(Concurrency)적으로 진행합니다.

로딩화면(빙글빙글)0.1초 -> 데이터수신0.1초 -> 로딩화면(빙글빙글)0.1초 -> 데이터수신0.1초 ->..데이터수신완료

 

- 동기는 말 그대로 동시에 일어난다는 뜻입니다.

- 비동기는 말 그대로 동시에 일어나지 않는다를 의미합니다.

이 표현이 왜 오해를 살 수 있는지 아실 것 같나요?

해당 표현은 마치, 동기의 작업방식을 비동기처럼 작성했고, 비동기의 작업방식을 순차적인것으로 오해할 수 있습니다.

실제 동작과 정말 극단적으로 반대되는 결과가 나올 수 있다는 거겠죠?

 

 

동기로 프로그래밍 했을 때 장점과 단점

<장점>

단일 Task 를 빠르게 처리할 수 있다.

<단점>

1. UI 를 그리는 작업중에 데이터수신이 발생하면 UI 가 멈춘것처럼 보이는 결과가 나오는데, GUI 프로그램에서는 치명적인 단점으로 다가온다.

2. 공유자원에 대한 문제가 발생한다.

 

비동기로 프로그래밍 했을 때 장점과 단점

<장점>

여러개의 Task 를 동시에 처리할 수 있다.

GUI 프로그래밍에서 UI 가 멈춘것처럼 보이는 결과를 방지한다.

<단점>

병행이 아니라, 병렬인 경우 각 Task 처리에 대한 속도가 느려진다.

역시 공유자원에 대한 문제가 발생한다.

 

+ 동기화(synchronization)

서로 다른 Task 가 공유자원을 사용하는 경우 하나의 자원의 일관성이 깨질 수 있습니다.

그에 따라 공유자원을 사용할 때는 일관성이 깨지지 않도록 해야합니다.

이러한 작업을 동기화 작업이라고 하며, 이 과정은 결코 쉽지 않습니다.

 

오늘의 결론

자신이 만들고자 하는 프로그램이 동기or비동기 중 어느 것으로 가야하는지 잘 생각하여 결정하고,

동기화처리를 해주어야 하는 공유자원이 있는지에 대해서도 신경을 써주어야합니다.

static 변수는 많은 단점이 있지만, 그 중 공유자원으로 사용되면서 이같은 문제를 발생시킬 수 있습니다.

static 변수가 아니더라도, 공유자원으로 사용되면서 이같은 문제를 발생시킬 수 있습니다.

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

GC ( Garbage Collection )  (0) 2021.04.28
동시성 이슈  (0) 2021.04.27
쓰레드(Thread) / 프로세스(Process)  (0) 2021.04.27
HTTP (HyperText Transfer Protocol)  (0) 2021.04.26
자료구조(Data Structure)  (0) 2021.04.26

+ Recent posts