특정 조건에서만 이벤트를 필터링하거나 출력하려는 ​​경우 조건문을 사용할 수 있다.

 

Logstash의 조건문은 프로그래밍 언어에서와 같은 방식으로 작동한다. 

if EXPRESSION {
  ...
} else if EXPRESSION {
  ...
} else {
  ...
}

//expression(표현식)은 비교테스트, boolean logic등을 의미한다.

 

비교 연산자는 다음과 같다.

  • 동등성(equality): ==, !=, <, >, <=, >=
  • 정규표현식(regexp): =~, !~    ->  ex) a =~ b (왼쪽의 문자열 값 a에 대해 오른쪽 b의 패턴을 확인)
  • 포함(inclusion): in, not in

단항 연산자

  • !

표현식은 길고 복잡해질 수 있다.

표현식은 다른 표현식을 포함할 수 있고, !로 표현식을 부정할 수도 있으며 ()로 표현식을 묶을 수도 있다.

 


조건문을 다양한 곳에 사용할 수 있지만, 특별한 field인 @metadata 필드에 조건문을 적용해보려 한다.

먼저 @metadata 필드에 대해 알아보자.

@metadata의 내용은 @metadata 출력 시 이벤트의 일부가 아니며, 조건문에 사용하거나 필드 참조 및 sprintf서식을 사용하여 이벤트 필드를 확장 및 구축하는 데 유용하다.

 

아래의 conf파일에 대해 알아보자.

먼저 표준입력(stdin)에서 이벤트를 생성하고, 무엇을 입력하든 message이벤트의 필드가 된다. filter block의 mutate이벤트는 몇 개의 필드를 추가하고 일부는 필드에 중첩된다.

 

input {
    stdin { }
}

filter {
    mutate { add_field => { "show" => "This data will be in the output" } }
  mutate { add_field => { "[@metadata][test]" => "Hello" } }
  mutate { add_field => { "[@metadata][no_show]" => "This data will not be in the output" } }
}

output {
    if [@metadata][test] == "Hello" {
        stdout { codec => rubydebug}
    }
}

위의 conf파일을 실행해보자

실행후 "asdf"를 입력하는 경우 아래와 같은 결과가 나온다.

입력한 "asdf"는 message필드 내용이 되었고, 조건문은 test필드 내에 중첩된 @metadata 필드의 내용을 성공적으로 평가했다. 그러나 output에는 @metadata 필드나 @metadata의 내용이 표시되지 않았다.

이때, rubydebug코덱을 사용하여  metadata => true로 해주면 @metadata 필드의 내용을 output에 보여줄 수 있다.

 

수정한 conf파일을

실행시키고 똑같이 "asdf"를 입력하면 아래와 같은 결과가 나옴을 확인할 수 있다.

 

위의 내용을 보면 @metadata 필드와 @metadata필드의 하위 내용들을 볼 수 있음을 확인할 수 있다.

'ElasticSearch' 카테고리의 다른 글

Elastic Stack에서 한국어 NLP사용  (0) 2022.06.02
매일 ES에 있는 data를 CSV파일로 만들기  (0) 2022.04.26
ElasticStack에 대해  (0) 2022.03.15

RDBMS에 있는 data를 스케줄링과 쿼리를 사용해 Elasticsearch로 접근하는 다양한 플러그인이Logstash에 존재한다. 

참고: https://wooyoung85.tistory.com/52     

 

하지만 RDBMS가 아닌 Elasticsearch에서 매일 새로 유입된 data만  또다른 elasticsearch를 거쳐서 logstash를 통해 output 파일로 만들 수 있는지 확인하고자 했다. 

 

Trouble Shooting

 

 

input에 file과 elasticsearch 2개를 설정

 

※ 아주 기본적인 사실이지만 하나의 logstash에서 2개의 input을 받을 수 없다. 하지만 output은 병렬처리가 가능하다.

 

elasticsearch에 저장한 index를 logstash의 schedule기능을 통해 하루치의 data만 output으로 만든다.

 

이때 output 파일은 csv파일로 만들기로 정했다.

 

 

우선, logstash의 scedule기능은 cron문법과 유사하다. 

 

▶cron 표현식

 *  *  *  *  *    수행할 명령어
┬ ┬ ┬ ┬ ┬
 │   │    │   │   │
 │   │    │   │   │
 │   │    │   │  └───────── 요일 (0 - 6) (0:일요일, 1:월요일, 2:화요일, …, 6:토요일)
 │   │    │ └───────── 월 (1 - 12)
 │   │   └───────── 일 (1 - 31)
 │  └───────── 시 (0 - 23)
└───────── 분 (0 - 59)

 

logstash conf파일

input: 엘라스틱 인덱스 (elastic홈페이지에 있는 sample data 활용)
output: csv파일(필드로 _id값, _source안에있는 FlightNum DestCountry timestamp OriginCountry) 

=> 새로 유입된 데이터를 지속적으로 체크할 수 있도록 로그스태시 스케줄 기능과 쿼리를 이용

위의 파일에서 %{_id}부분이 csv파일에 나오지 않았다.

왜냐하면 _index, _type, _id, _score의 값들은 Elasticsearch만을 위한 값이기 때문에 따로 export할 수 없는 값들이다.

이때, 값으로 나올 수 없는 값들은 logstash에서 공백으로 처리한다.

 

+

※ timestamp와 @timestamp 구분
timestamp: 클라이언트 요청을 마친 시간 -> web log 시간
@timestamp: logstash에서 전처리 과정을 걸친 날짜데이터 -> parsing 된 시간

 

따라서 1분 단위로 elasticsearch에 새롭게 들어온 data를 csv파일로 만드는 conf파일은 다음과 같다.

 

잘못된 내용이나 추가할 내용은 댓글로 남겨주시면 감사하겠습니다~!

'ElasticSearch' 카테고리의 다른 글

Elastic Stack에서 한국어 NLP사용  (0) 2022.06.02
조건문 사용(If Else 문)  (0) 2022.04.29
ElasticStack에 대해  (0) 2022.03.15
 

해당 포스팅 내용은 책 'Clean Code - Robert C. Martin' 출처로 합니다.

 

 

동시성과 깔끔한 코드는 양립하기 어렵다.

 

동시성이 필요한 이유

동시성이란?
· 결합(coupling)을 없애는 전략
· 무엇(what)과 언제(when)을 분리하는 전략

무엇
언제를 분리하면
· 애플리케이션 구조와 효율이 극적으로 나아진다.
 - 구조적인 관점에서 프로그램은 거대한 루프 하나가 아닌 작은 협력 프로그램 여럿으로 보이며, 이로 인해 시스템을 이해하기 쉬워지고 문제를 분리하기도 쉬워진다.

· 응답시간과 작업 처리량 개선에도 도움이 된다
- crawler같이 요청하고 오래 기다려야 하는 작업을 하는 경우, 여러 작업을 병렬적으로 해도 좋은 경우

 

 

동시성에 대한 오해

 

1. 동시성은 항상 성능을 높여준다.

동시성은 때로 성능을 높여준다. 대기 시간이 아주 길어 여러 스레드가 프로세서를 공유할 수 있거나,
여러 프로세서가 동시에 처리할 독립적인 계산이 충분히 많은 경우에만 성능이 높아진다.

2. 동시성을 구현해도 설계는 변하지 않는다.

-> 단일 스레드 시스템과 다중 스레드 시스템은 설계가 판이하게 다르다.

3. 웹 또는 EJB 컨테이너를 사용하면 동시성을 이해할 필요가 없다.
→ 컨테이너가 어떻게 동작하는지, 어떻게 동시 수정, 데드락 등과 같은 문제를 피할 수 있는지 알아야 한다.

 

 

동시성에 대한 타당한 생각

1. 동시성은 다소 부하를 유발한다.
→ 성능 측면에서 부하가 걸리며, 코드도 더 짜야 한다.

2. 동시성은 복잡하다.
→ 간단한 문제라도 동시성은 복잡하다.

3. 일반적으로 동시성 버그는 재현하기 어렵다.
→ 진짜 결함으로 간주되지 않고 일회성 문제로 여겨 무시하기 쉽다.

4. 동시성을 구현하려면 근본적인 설계 전략을 재고해야 한다.

 

 

동시성을 구현하기 어려운 이유

public class X{
	private int lastIdUsed;
    
    public int getIntNextId(){
    	return ++lastIdUsed;
    }
 }

위의 코드를 살펴보자.
인스턴스 X를 생성하고, lastIdUsed 를 42로 설정한 다음, 두 스레드가 해당 인스턴스를 공유한다.
두 스레드가 getNextId();를 호출한다고 가정할 때, 결과는 셋 중 하나다.

  • 한 스레드는 43을 받는다. 다른 스레드는 44를 받는다. lastIdUsed는 44가 된다.
  • 한 스레드는 44을 받는다. 다른 스레드는 43을 받는다. lastIdUsed는 44가 된다.
  • 한 스레드는 43을 받는다. 다른 스레드는 43를 받는다. lastIdUsed는 43이 된다.

객체 하나를 공유한 후 동일 필드를 수정하던 두 스레드가 서로 간섭하므로 예상치 못한 결과를 내놓는다.
(두스레드가 자바 코드 한 줄을 거쳐가는 경로까지 따진다면 잠재적인 경로는 수없이 많다.)

 

 

동시성 방어 원칙

동시성 코드가 일으키는 문제로부터 시스템을 방어하는 원칙과 기술

 

 단일 책임 원칙(SRP)

동시성 코드는 다른 코드와 분리해야 한다.

 

고려할 사항

  • 동시성 코드는 독자적인 개발, 변경, 조율 주기가 있다.
  • 동시성 코드에는 독자적인 난관이 있다. 다른 코드에서 겪는 난관과 다르며 훨씬 어렵다.
  • 잘못 구현한 동시성 코드는 별의별 방식으로 실패한다.

따름 정리(자료 범위를 제한하라)

자료를 캡슐화 해야하고, 공유자료를 최대한 줄여야 한다.

 

공유 객체를 사용하는 코드 내 임계영역(critical section)을 synchronized 키워드로 보호하라.
critical section의 수를 줄이는 기술이 중요하다. 

 

공유자료를 수정하는 위치가 많을수록

  • 보호할 critical section을 빼먹는다. 그래서 공유 자료를 수정하는 모든 코드를 망가뜨린다.
  • 모든 critical section을 올바로 보호했는지 확인하느라 똑같은 수고를 반복한다.
  • 찾아내기 어려운 버그가 더욱 찾기 어려워진다.

따름 정리(자료 사본을 사용해라)

 

  • 공유 자료를 줄이려면 처음부터 공유하지 않는 방법이 제일 좋다.
  • 객체를 복사해 읽기 전용으로 사용한다.
  • 각 스레드가 객체를 복사해 사용한 후 한 스레드가 해당 사본에서 결과를 가져오는 방법을 사용한다.

 

따름 정리(스레드는 가능한 독립적으로 구현해라)

독자적인 스레드로, 가능하면 다른 프로세서에서, 돌려도 괜찮도록 자료를 독립적인 단위로 분할하라.

 

각 스레드는 다른 스레드와 자료를 공유하지 않고, 클라이언트 요청 하나를 처리하도록 구현해라.
모든 정보는 비공유 출처에서 가져오며 로컬 변수에 저장한다.

 

 

라이브러리를 이해하라

■언어가 제공하는 클래스를 검토하라.

   자바에서는 
  java.util.concurrent
  java.util.concurrent.atomic
  java.util.concurrent.locks
 를 익혀라.

 

자바 5로 스레드 코드를 구현한다면 고려해야 할 것들

  • 스레드 환경에 안전한 컬렉션을 사용한다. (자바 5부터 제공)
  • 서로 무관한 작업을 수행할 때는 executer 프레임워크를 사용한다.
  • 가능하면 스레드가 차단(blocking)되지 않는 방법을 사용한다.
  • 일부 클래스 라이브러리는 스레드에 안전하지 못하다.

 

스레드 환경에 안전한 컬렉션 java.util.concurrent

java.util.concurrent패키지가 제공하는 클래스는 다중 스레드 환경에서 사용해도 안전하며, 성능도 좋다.
ConcurrentHashMap은 거의 모든 상황에서 HashMap보다 빠르다.
동시 읽기/쓰기를 지원하며, 자주 사용하는 복합 연산을 다중 스레드 상에서 안전하게 만든 메서드로 제공한다.

 

좀 더 복잡한 동시성 설계를 지원하기 위한 클래스

ReentrantLock 한 메서드에서 잠그고 다른 메서드에서 푸는 락(lock)이다.
Semaphore 전형적인 세마포어다. 개수(count)가 있는 락이다.
CountDownLatch 지정한 수만큼 이벤트가 발생하고 나서야 대기 중인 스레드를 모두 해제하는 락이다. 모든 스레드에게 동시에 공평하게 시작할 기회를 준다.

 

실행모델을 이해하라

  • 기본 용어

 

용어 의미
한정된 자원 (Bound Resource) 다중 스레드 환경에서 사용하는 자원. 크기나 숫자가 제한적이다.
ex) 데이터베이스 연결, 길이가 일정한 읽기/쓰기 버퍼
상호 배제 (Mutual Exclusion) 한 번에 한 스레드만 공유 자료나 공유 자원을 사용할 수 있는 경우를 가리킨다.
기아 (Starvation) 스레드가 오래 혹은 영원히 자원을 기다린다.
ex) 짧은 스레드에게 우선순위를 준다면, 긴 스레드가 기아 상태에 빠진다.
데드락 (Deadlock) 스레드가 서로 끝나기를 기다린다. 모든 스레드가 각기 필요한 자원을 다른 스레드가 점유해 어느쪽도 진행하지 못한다.
라이브락 (Livelock) 락을 거는 단계에서 각 스레드가 서로를 방해한다. 오래 혹은 영원히 진행하지 못한다.

 

다중 스레드 애플리케이션에서 사용하는 실행 모델

아래의 기본 알고리즘과 각 해법을 이해하라.

 

1. 생산자-소비자 (Producer-Consumer)

  • 생산자 스레드가 정보를 생성해 버퍼나 대기열에 넣고, 소비자 스레드는 대기열에서 정보를 가져와 사용한다.
  • 스레드가 사용하는 대기열은 한정된 자원이며 생산자 스레드는 정보를 채운 다음 소비자 스레드에게, 소비자 스레드는 대기열에서 정보를 읽어들인 후 생산자 스레드에게 시그널을 보낸다.
  • 잘못하면 생산자 스레드와 소비자 스레드가 둘 다 진행 가능함에도 불구하고 동시에 서로에게서 시그널을 기다릴 가능성이 존재한다.

2. 읽기-쓰기 (Readers-Writers)

 

  • 읽기 스레드와 쓰기 스레드 중 어느 곳에 우선권을 주는 모델.
  • 처리율을 강조하면 기아현상이 생기거나 오래된 정보가 쌓이고 갱신을 허용하면 처리율에 영향을 미친다.
  • 간단한 전략은 읽기 스레드가 없을 때까지 갱신을 원하는 쓰기 스레드가 버퍼를 기다리는 방법. (단, 읽기 스레드가 계속 이어진다면 쓰기 스레드는 기아 상태에 빠진다.)
  • 양쪽 균형을 잡으면서 동시 갱신 문제를 피하는 해법이 필요하다.

3. 식사하는 철학자들 (Dining Philosophers)

 

  • 락을 이용하여 스레드의 접근을 제한하는 방식의 모델.
  • 기업 애플리케이션은 여러 프로세스가 자원을 얻으려 경쟁한다.
  • 주의해서 설계하지 않으면 데드락, 라이브락, 처리율 저하, 효율성 저하 등을 겪는다.

 

동기화하는 메서드 사이에 존재하는 의존성을 이해하라

■공유 객체 하나에는 메서드 하나만 사용하라.

 

동기화하는 메서드 사이에 의존성이 존재하면 동시성 코드에 찾아내기 어려운 버그가 생긴다.
자바 언어는 개별 메서드를 보호하는 synchronized를 지원한다.

만약, 공유 객체 하나에 여러 메서드가 필요한 경우 세가지 방법을 고려한다.

  • 클라이언트에서 잠금
    클라이언트에서 첫 번째 메서드를 호출하기 전에 서버를 잠근다. 마지막 메서드를 호출할 때까지 잠금을 유지한다.
  • 서버에서 잠금
    서버에 '서버를 잠그고 모든 메서드를 호출한 후 잠금을 해제하는 메서드'를 구현한다. 클라이언트는 이 메서드를 호출한다.
  • 연결 서버
    잠금을 수행하는 중간 단계를 생성한다. '서버에서 잠금'방식과 유사하지만 원래 서버는 변경하지 않는다.

 

 동기화하는 부분을 최대한 작게 만들어라

자바에서 synchronized 키워드를 사용하면 락을 설정한다.
같은 락으로 감싼 모든 코드 영역은 한 번에 한 스레드만 실행이 가능하다.
락은 스레드를 지연시키고 부하를 가중시키므로, 여기저기 synchronized문을 남발하면 안 된다.

반면, critical section 반드시 보호해야 한다. 따라서 코드를 짤 때는 critical section의 수를 최대한 줄인다.
critical section 개수를 줄인다고 크기를 키우면 스레드 간에 경쟁이 늘어나고 프로그램 성능이 떨어진다.

 

 

올바른 종료 코드는 구현하기 어렵다

종료 코드를 개발 초기부터 고민하고 동작하게 초기부터 구현하라.
"but" 생각보다 어려우므로 이미 나온 알고리즘을 검토하라.

 

데드락이 가장 흔히 발생하는 문제이다.

 

스레드 코드 테스트하기

1. 문제를 노출하는 테스트 케이스를 작성하라.

2. 프로그램 설정과 시스템 설정과 부하를 바꿔가며 자주 돌려라.

3. 테스트가 실패하면 원인을 추적하라.(꼼꼼히 원인분석을 하는 것이 중요)

 

구체적인 지침

 

■말이 안 되는 실패는 잠정적인 스레드 문제로 취급하라.

시스템 실패를 '일회성'이라 치부하지 마라.

 

'일회성' 문제를 계속 무시할 경우 잘못된 코드 위에 코드가 계속 쌓인다.

 

■다중 스레드를 고려하지 않은 순차 코드부터 제대로 돌게 만들자.

스레드 환경 밖에서 생기는 버그와 스레드 환경에서 생기는 버그를 동시에 디버깅 하지 마라.
스레드 환경 밖에서 코드를 올바로 돌려라.

 

일반적인 방법의 경우, 스레드가 호출하는 POJO(순수 자바 객체)를 만들어 스레드 환경 밖에서 테스트한다.

 

다중 스레드를 쓰는 코드 부분은 다양한 환경에 쉽게 끼워넣을 수 있도록 구현하라.

다양한 설정에서 실행할 목적으로 구현하라.

 

  • 한 스레드로 실행하거나, 여러 스레드로 실행하거나, 실행 중 스레드 수를 바꿔본다.
  • 스레드 코드를 실제 환경이나 테스트 환경에서 돌려본다.
  • 테스트 코드를 빨리, 천천히, 다양한 속도로 돌려본다.
  • 반복 테스트가 가능하도록 테스트 케이스를 작성한다.

 

다중 스레드를 쓰는 코드부분을 상황에 맞춰 조절할 수 있게 작성하라.

 

적절한 스레드 개수를 조율하기 쉽게 코드를 구현한다.
프로그램이 돌아가는 도중에 스레드 개수를 변경하는 방법도 고려한다.
프로그램 처리율과 효율에 따라 스스로 스레드 개수를 조율하는 코드도 고민한다.

 

 

프로세서 수보다 많은 스레드를 돌려보라.

 

스와핑이 잦을수록 임계영역을 빼먹은 코드나 데드락을 일으키는 코드를 찾기 쉬워진다.

 

다른 플랫폼에서 돌려보라.

처음부터 그리고 자주 모든 목표 플랫폼에서 코드를 돌려라.

 

다중 스레드 코드는 플랫폼마다 다르게 돌아가기 때문에 코드가 돌아갈 수 있는 플랫폼 전부에서 테스트 하는 것이 마땅하다.

 

코드에 보조코드(instrument)를 넣어 돌려라. 강제로 실패를 일으키게 해보라.

스레드 코드에서  간단한 테스트로는 버그가 드러나지 않는다.
(수천 가지 경로 중 아주 소수만 실패하고, 실패하는 경로가 실행될 확률은 저조하기 때문)

 

드물게 발생하는 오류를 좀 더 자주 일으키려면, 보조 코드를 추가해 코드가 실행되는 순서를 바꿔준다.
ex) Object.wait(), Object.sleep(), Object.yield(), Object.priority()

 

각 메서드는 스레드가 실행되는 순서에 영향을 미치기 때문에 버그가 드러날 가능성도 높아진다.

 

코드에 보조 코드를 추가하는 두 가지 방법

1. 직접 구현하기

코드에다 직접 wait(), sleep(), yield(), priority() 함수를 추가한다.

-> 까다로운 코드를 테스트할 때 적합

2. 자동화

jiggle(흔들기) 기법을 사용해 오류를 찾아내라.

보조 코드를 자동으로 추가하려면 AOF, CGLIB, ASM 등과 같은 도구를 사용해야 한다.
ex) ThreadJigglePoing.jiggle()는 무작위로 sleep이나 yield를 호출한다.

 

결론:

깔끔한 접근 방식을 취한다면 코드가 올바로 돌아갈 가능성이 극적으로 높아 진다.

  • 다중 스레드 코드는 올바로 구현하기 어렵다. 그러므로 각별히 깨끗하게 코드를 짜야 한다.
  • SRP 준수하여야 하며 POJO를 사용해 스레드를 아는 코드와 스레드를 모르는 코드를 분리한다.
  • 스레드 코드를 테스트할 때는 전적으로 스레드만 테스트한다
  • 동시성 오류를 일으키는 잠정적 원인을 철저히 이해한다.
  • 사용하는 라이브러리와 기본 알고리즘을 이해한다.
  • 보호할 코드 영역을 찾아내는 방법과 특정 코드 영역을 잠그는 방법을 이해한다.
  • 어떻게든 문제는 생긴다. 초반에 드러나지 않는 문제는 일회성으로 치부해 무시하면 안된다.
  • 스레드 코드는 많은 플랫폼에서 많은 설정으로 반복해서 계속 테스트해야 한다.
  • 시간을 들여 보조 코드를 추가하면 오류가 드러날 가능성이 크게 높아진다.

참고:

http://amazingguni.github.io/blog/2016/07/Clean-code-13-%EB%8F%99%EC%8B%9C%EC%84%B1

https://velog.io/@lychee/Clean-Code-13%EC%9E%A5-%EB%8F%99%EC%8B%9C%EC%84%B1-kr50589w

https://haeng-on.tistory.com/74 

'Clean Code' 카테고리의 다른 글

[Clean Code] Chapter 18 동시성 2  (0) 2022.06.02
[Clean Code] Chapter 12 창발성  (0) 2022.03.21
Chapter 7 오류 처리  (0) 2022.02.09
Chapter 2 의미있는 이름  (0) 2022.01.13

해당 포스팅 내용은 책 'Clean Code - Robert C. Martin' 출처로 합니다.

 

 

 

먼저 창발성이 무엇인지에 대해 알아보자

 

창발: 하위 계층(구성 요소)에는 없는 특성이나 행동이 상위 계층(전체 구조)에서 자발적으로 돌연히 출현하는 현상
창발성: 단순한 결합이 복잡한 결과를 나타내는 특징을 의미 
  - 창발적 설계란 ?
-> 어떤 규칙과 원칙에 따라 설계를 하였을 때, 그것들이 모여 아주 좋은 거시적 설계가 되었을 때 이를 창발적 설계라 한다. 

 

소프트 웨어의 설계 품질을 크게 높여주는 4가지

중요도 순

  • 모든 테스트를 실행한다.
  • 중복을 없앤다.
  • 프로그래머의 의도를 표현한다.
  • 클래스와 메서드 수를 최소로 줄인다.

 

위와 같은 규칙을 따를 때, 켄트벡은 설계가 '단순하다' 라고 말한다.

이러한 규칙을 잘 지킨다면,

1. 우수한 설계가 나온다.

2. 코드 구조와 설계를 파악하기가 쉬워진다.

3. SRP나 DIP와 같은 원칙을 적용하기 쉬워진다.

4. 우수한 설계의 창발성을 촉진한다.

 

 

단순한 설계 규칙1: 모든 테스트를 실행하라

  • 설계는 시스템이 의도한 대로 돌아가는지 검증할 수 있어야 한다.
  • 테스트가 가능한 시스템을 만들려는 노력은 낮은 결합도와 높은 응집력이라는 결과를 만든다.
  • 테스트 케이스를 만들고 계속 실행함으로써, 설계 품질을 자연스레 높일 수 있다.

단순한 설계 규칙2: 중복을 없애라

  • 중복이란 추가 작업, 추가 위험, 불필요한 복잡도를 뜻한다.
  • 깔끔한 시스템을 만들기 위해서는 단 몇 줄이라도 중복을 제거하겠다는 의지가 필요하다.

        동일한 코드를 새 메서드로 만듦으로써, 

        1. 메서드의 가시성↑

        2. 재사용성 ↑ -> 시스템 복잡도 ↓

 

  • Template Method 패턴은 고차원 중복 제거할 목적으로 자주 사용된다.

   Template Method 패턴: 거의 동일한 두 메소드에서 중복되는 부분을 합치고, 차이가 있는 부분만 별도의 하위 클래스에 작성하는 방법

 

단순한 설계 규칙3: 의도를 분명히 표현하라

  • 엉망인 코드를 짠다면 타인 또는 미래의 나 또한 코드를 100% 이해가 힘들어진다.
  • 개발자가 코드를 명백하게 짠다면, 다른 사람이 코드를 이해하기 쉬워지고 그 결과 결함이 줄고 유지보수 비용이 적게 든다.
의도를 분명히 표현하는 방법

1. 좋은 이름을 선택한다.
2. 함수와 클래스의 크기를 가능한 줄인다.
3. 표준 명칭을 사용한다.
4. 단위 테스트 케이스를 꼼꼼히 작성한다.
  • 의도를 분명하게 표현하기 위해서는 결국 노력이 가장 중요하다.

 

단순한 설계 규칙4: 클래스와 메서드 수를 최소로 줄여라

  • 중복을 제거하고 의도를 명백히 표현하는 등의 기본 원칙을 극단적으로 따르면, 득보다 실이 많아질 수 있다.(비효율적이 될 가능성↑)
  • 단순한 설계 규칙1~3 까지를 지키되, 적당한 선에서 실용적인 방식을 택하는 것이 중요하다.  

 

 

참고: https://velog.io/@jwkim/Clean-Code-12%EC%9E%A5-%EC%B0%BD%EB%B0%9C%EC%84%B1

'Clean Code' 카테고리의 다른 글

[Clean Code] Chapter 18 동시성 2  (0) 2022.06.02
[Clean Code] Chapter 13 동시성  (0) 2022.03.22
Chapter 7 오류 처리  (0) 2022.02.09
Chapter 2 의미있는 이름  (0) 2022.01.13

Apache Kafka란?

  • 실시간으로 기록 스트림을 게시, 구독, 저장 및 처리할 수 있는 분산 이벤트 스트리밍 플랫폼
  • Pub-Sub 모델의 메세지 큐
  • 분산된 환경에 특화되어 있음

 

먼저, 이벤트 스트림 플랫폼에 대하여 알아보자

구조는 아래와 같다.

- 이벤트:

  • 어떤 일이 일어났을 때 그 사건을 의미(레코드 혹은 메시지)
  • kafka에서 데이터를 읽거나 쓰는 기본 단위

하나의 이벤트 단위 안에 event key, event value, event time stamp가 존재하고 선택 옵션으로 메타 헤더 데이터가 존재

 

- 이벤트 스트림:

  • 연속적으로 생성되는 이벤트 흐름

- 이벤트 스트림 처리:

  • 연속적인 이벤트 스트림을 처리하거나 분석 하는것

- 이벤트 스트리밍:

  • 이벤트 스트림 처리 작업의 일부분으로 다른 시스템에서 쉽게 액세스하고 분석할 수 있도록 장소간에 이벤트 데이터를 효율적으로 이동하는 프로세스

 

Kafka에 대한 설명

 1. 구성 요소

 

 

2. 모델: Pub-Sub 모델

 

prducer: 데이터를 보내는 쪽

consumer: 데이터를 가져가는 쪽

중간에 메세징 서버를 두고, 데이터를 주고받는 형태인 Pub-Sub 모델을 사용

pub-sub 모델의 단점:

  1. 중간에 메세징 시스템이 있어 메세지 전달 속도가 느림
  2. 직접 통신을 하지 않기 때문에 데이터가 정확히 전달되었는지 확인 불가능

해결책:

  1. 메세지 교환 신뢰성 관리를 producer와 consumer쪽으로 넘김
  2. 부하가 많이 걸리는 교환기 기능을 consumer가 만들 수 있게 함
  3. kafka는 메세지를 파일시스템으로 저장하기 때문에 메세지를 많이 쌓아도 성능이 크게 감소하지 않음

 


3. Kafka 동작원리

 

카프카에 저장되는 메시지는 topic으로 분류되고, topic 여러 개의 partition으로 나뉘어 진다.
 offset:
 - partition에는 message 상대적 위치를 나타냄

 - offset 정보를 이용해 이전에 가져간 메시지의 위치 정보를 있고, 
   동시에 들어오는 많은 데이터를 여러 개의 파티션에 나누어 저장하여 병렬로 빠르게 처리



 

      Producer에서 생산한 메시지는 여러 개의 파티션에 저장
->
메시지를 처리하는 consumer 쪽에서도 여러 Consumer가 메시지를 처리하는 것이 훨씬 효율적

Consumer Group : 하나의 Topic을 처리하기 위한 여러 Consumer들을 지칭

Topic의 Partition은 Consumer Group과 1:n 매칭을 해야 한다.
    (자신이 읽고 있는 파티션에는 같은 그룹내 다른 Consumer가 읽을 없다.)


위의 그림은 partition개수와 consumer개수에 따른 동작하는 방식을 설명한다.





kafka에서 특정 broker에 문제가 발생한 경우


해결책:
해당 broker 역할을 다른 broker에서 즉각적으로 대신 수행할 있게 하기 위해 replication 기능이 존재

- 모든 데이터 생산과 처리는 leader topic에서 이루어진다.

- 만약 leader topic 문제가 발생할 , leader 동기화를 유지하던 follow topic leader topic 대체하여 

   데이터 생산과 처리 역할을 수행하게 된다.


결론:
이와 같은 방식에는 데이터 유실이 없다는 장점이 존재하지만, 복제를 하기 위한 시간과 

        네트워크 비용이 발생하기 때문에 데이터의 중요도에 따라 ack 옵션으로 세부사항을 설정하는 것이 좋다.



4. Kafka의 필요성

 

 

  Kafka 이전의 아키텍쳐

문제점:

  • 시스템간의 의존성이 복잡하여 만약 한 시스템에서 장애가 발생하더라도 여러 시스템에 영향이 가며 이는 장애를 복구하는데 있어서도 매우 큰 걸림돌이 된다.(point-to-point 메세지 전달 방식)
  • 새로운 시스템이나 DB를 도입하고 재구성하기 어렵다.
  • 각 애플리케이션은 고유의 포맷을 가지고 있어 데이터 변환 시스템이 필요하다.
  • 서버에 많은 이벤트가 들어온다면 대기 시간이 길어진다.

 

kafka 적용 후

 

  • 복잡해진 아키텍쳐 단순화
  • 애플리케이션간의 데이터 교환이 쉬워짐
  • 즉시 또는 현재 시간내에 처리할 수 있에 대기시간이 단축
  • 보편적인 데이터 파이프라인을 지속하기 위해서 이미 존재하는 시스템과 연결하는 Kafka Connect라는 프레임워크 제공

“but” kafka의 단점 또한 존재한다.

 

   단점:

  • Message tweaking issues : 카프카는 byte를 받고 보내기만 하는데, 메시지가 수정이 필요하다면 카프카의 퍼포먼스는 급격히 감소한다. 따라서 메시지가 바뀔필요가 없을때 카프카는 가장 잘 동작한다.
  • wild card topic selection을 지원하지 않는다. 즉, 한번에 하나의 topic만 선택이 가능하다.
  • Reduce Performance : producer가 데이터를 압축하고 consumer가 데이터를 decompress 하는 과정에서 성능 저하가 생길수 있다.

 

 

Conflent란?

설립: 2014년 11월 LinkedIn에서 kafka를 만들던 일부 엔지니어들이 kafka에 집중하기 위해 Confluent라는 새로운 회사 창립

 

목적: kafka를 엔터프라이즈 환경에 맞게 만드는 것을 목표로 하여 여러 컴포넌트를 추가로 제공

 

1. Schema Resitry

   - 표준 스키마를 사용하여 개발

   - 운영의 복잡성 감소

 

2. Control Centerd

  - 운영자: 멀티 클러스터 환경과 보안을 중앙 관리 및 통제

  - 개발자: 메시지, 토픽, 스키마를 확인 및 커넥터 관리, ksqlDB 쿼리 작성 가능

 

3. Self Balencing

  - 동적으로 브로커별 처리량 최적화

 

4. ksqlDB

  - SQL문을 통해 데이터 스트림 쿼리 작업의 용이성 높임

 

confluent의 성장 

 

confluent의 경쟁 솔루션

 


출처: 

https://leediz.tistory.com/2

https://www.tibco.com/ko/reference-center/what-is-event-stream-processing

https://jaegukim.github.io/posts/kafka-장단점/

https://www.confluent.io/press-release/confluent-experiences-rapid-growth-as-companies-adopt-event-streaming

https://blog.publiccomps.com/confluent

Elastic Stack: Elastic 회사에서 제공하는 4개의 소프트웨어로 구성된 빅데이터 파이프라인
                        추가적으로, X-Pack(보안, 알림, 모니터링, 머신러닝등의 기능)과 Elastic Cloud를 제공한다.


 v5.0.0 이전 - ElasticSearch + Logstash + Kibana 로 이루어진 서비스 - ELK Stack 으로 불림
 v5.0.0 이후 - Beats 를 시작으로 X-Pack과 Elastic Cloud 가 포함되어 Elastic Stack으로 불림

 

 

 

 

Elastic Stack의 구조

 

Beats: 데이터 수집
LogStash: 데이터 수집 및 집계, 전송
ElasticSearch: 데이터 저장, 색인, 분석
Kibana: 데이터 분석 및 시각화

-> Logstash나 Beats를 통해 모든 데이터를 수집하고, ElasticSearch를 통해 수집된 데이터의 검색, 처리, 저장하며 Kibana를       통해 데이터를 시각화

 

 

 

 

 

 

1. ElasticSearch

Elastic Stack의 중심

ElasticSearch란?
▶루씬(Lucene) 기반으로 개발된 분산형 오픈소스 RESTful API 검색엔진

      ▧ 루씬: 자바로 이루어진 정보 검색 오픈소스 SW이자 고성능 정보 검색 라이브러리

▶역할 : 
          ◎모든 유형의 데이터(숫자, 문자열, ip, geo, date 등)를 색인하여 저장, 검색 집계를 수행
          ◎이를 통해 결과를 클라이언트 및 다른 프로그램에 전달

    

 

그럼에도 왜 우리는 루씬대신 ElasticSearch를 사용할까?

루씬이 검색과 색인이 필요한 강력한 API를 제공하는 것은 맞다.

하지만, 결국 루씬은 어플리케이션이 아닌 라이브러리 ,따라서 사용자는 원하는 부분과 기능은 다 직접 개발이 필요하다.

 

ElasticSearch의 특징은 다음과 같다.

  1. SchemaLess
  2. 전문검색(Full text Search) 
  3. NRT(Near Real Time)
  4. RestFul API
  5. 클러스터식 구성 

 

v7.x : type 구조의 삭제
v8.x: v7.x REST API 호환이 되도록 하였고, 또한 보안기능 기본적으로 활성화시킴

1.SchemaLess 

여기서 SchemaLess는 스키마가 없다는 뜻이 아니라 동적으로 스키마가 생성된다는 의미이다.

 

DBMS는 데이터를 입력하기 전에, 테이블을 생성과 칼럼 정의 등 사전 작업이 필요하다.

하지만 ElasticSearch는 데이터를 입력하기 전에, 데이터의 어떤 필드를 저장할 것인지 사전에 정의가 필요하지 않다.

왜냐하면 ElasticSearch는 자동으로 해당 데이터를 분석한 후에, 동적으로 스키마를 생성하기 때문이다.

 

 

2. 전문검색(Full text Search) 

ElasticSearch는 역색인 구조를 가지고 있다.

역색인 구조를 통해 찾고자 하는 데이터가 어느 도큐먼트에 있는지 빠르게 검색 가능하다.

 

●Elastic Search와 RDBMS의 비교

RDBMS ElasticSearch
schema mapping
database index
table type
row document
column field

 

 

 

 

 

 

 

역색인 구조 - 1. 공백을 기준으로 text를 Tokenize

                       2. 토큰 별로 어느 문서에 있는지 기록

 

                       -> 검색으로 찾고자 하는 데이터가 어느 문서에 저장되어 있는지 빠르게 검색 가능

 

추가적으로 아래와 같은 특징들이 존재한다.

  • 불규칙한 구조의 text도 색인 생성을 통하여 검색
  • 다양한 데이터 타입 제공
  • 관계형 데이터베이스에서 불가능한 비정형 데이터의 색인과 검색이 가능(빅데이터 처리에서 중요!)
  • 텍스트를 여러 단어로 변형하거나 텍스트의 특질을 이용한 동의어나 유의어를 활용한 검색이 가능
  •  형태소 분석을 통한 자연어 처리가 가능

 

3. Near Real Time(NRT)

거의 실시간으로 분석이 가능한 System이며, 색인과 검색이 거의 동시에 이루어진다.

기존의 하둡 시스템과 달리 ES cluster가 실행되는 동안에는 꾸준히 데이터가 입력(indexing) & 동시에 1초후 바로 검색 및 집계가 가능

 

4. RestFul API

표준 인터페이스(REST API)를 기본으로 지원, 

Data에 대한 http Method를 통해 CRUD를 수행 

 

5. Cluster 구성

대용량의 데이터 증가에 따른 Scale-Out & 데이터 Integrity(무결성) 유지를 하기 위한 구성

한 클러스터 내부에 하나 이상의 노드가 존재 

- cluster : 여러 대의 컴퓨터 혹은 구성 요소들을 논리적으로 결합하여 하나의 컴퓨터처럼 사용할 수 있도록 하는 기술

  • 여러 노드를 한 클러스터 내에 생성 - 부하 분산
  • 여러 노드 중 한 노드 장애 발생 시, 다른 노드들로 클러스터 재구성 - 안전성 확보
  • 여러 노드에 Data Replica(복제본) 생성 - 안전성 확보 

   → 운영 및 확장성 용이

 

 

ElasticSearch의 장점

 

  • 유연성 & 호환성 
    • Elasticsearch,Logstash,Kibana는 각각의 역할을 담당하기 때문에, 용도별로 분리해 version Up 및 발전하는 솔루션             - 구조적 안정성까지 보장
  • 스키마가 자유롭다
  • 수평 확장성
    • 클러스터에 노드를 추가함으로써, 수평적으로 확장이 용이 
      • 부하 분산 및 안전성 확보 
      • 운영 및 확장성이 용이 
  • 사전에 준비된 다양한 부가기능
    • Kibana 를 통한 UI 및 다양한 부가기능 제공 - 사전의 다른 작업 요구 X
  • ES 버전 별 Bulk 방법 차이 완화
    • Bulk = (RDBMS)의 Insert
    • ES는 버전 별로 벌크 방법의 차이가 존재한다.
    • 하지만 Logstash를 통하여 ES 핸들링으로 벌크 문제를 해결

 

ElasticSearch 단점

  • 진입 장벽이 존재
  • Document 간 조인을 수행할 수 없다. (두번 쿼리로 해결해야 한다.) -> "but" ES는 애초에 조인을 할 필요가 없다.
  • 트랜잭션 및 롤백이 제공되지 않는다.
  • 진정한 의미의 실시간 처리가 불가능하다. (색인된 데이터는 1초 뒤에 검색이 가능하다.)
  • 진정한 의미의 업데이트를 지원하지 않는다. (물론 있긴 하나, 삭제했다가 다시 만드는 형태)

 

Search Engine의 시장 현황

Elastic Stack과 Splunk 비교

공통점: Splunk와 ES의 주요 목표는 모두 시스템 로그 파일을 모니터링, 분석, 집계 및 시각화하는 것이다.

차이점:
            Splunk는 우수한 사용자 인터페이스를 갖춘 보다 편리한 솔루션이지만 사용자 라이선스를 구입하는 데 비용이 많이 든다.
            ES에 포함된 Kibana의 데이터 시각화는 초기 설정에 있어Splunk보다 덜 편리하지만 오픈 소스이기 때문에, 사용자 라이             선스 비용이 없고, ES가 Splunk에 비해 검색성능이 빠르다는 점이 강점이다.

결론: Splunk와 ELK 간의 처리 능력과 기능은 비슷하다. ELK는 구현 프로세스 초기에 설정하는 데 더 많은 작업과 계획이 필요하          지만 초기 설정 이후로는 ELK의 데이터 추출 및 시각화는 Splunk보다 사용자 친화적이고 검색 성능면에서는 월등하다.

 

 

 

2. Logstash

다양한 소스로부터 데이터를 수집하고 곧바로 전환하여 원하는 대상에 전송할 수 있도록 하는 경량의 오픈 소스 서버측 데이터 처리 파이프라인이다.

 

특징: 

 

  • 데이터를 수집하고 filter를 통하여 데이터를 변환(가공) 후,  ES 혹은 다른 데이터 저장소로 데이터를 전달                                        + 비정형 데이터를 쉽게 로드
  • 다양한 Plug-in이 지원 (유연한 플러그인 아키텍쳐)
  • 확장 및 실시간 데이터 파이프라인 구축에 유용 

 

 

Logstash의 데이터를 처리하기 위한 과정

1. Input
-  입력 데이터의 다양한 형태와 크기, source등을 지원한다.
 
2. Filter
- 데이터의 구문을 분석 및 변환 작업을 수행하며, 데이터의 형식이나 복잡성에 관계 없이 동적으로 변환한다.
  ex) grok filter - 비정형 데이터 구조 도출 / ip 주소 위치 좌표 해석을 하는 필터도 존재 

 3. Output
- ES를 포함한 다양한 데이터 저장소로 사용자의 요청에 알맞는 출력 형태로 전달

 

 

 

 

3. Kibana

 

 ElasticSearch의 데이터를 가장 쉽게 시각화할 수 있는 확장형 UI 도구

 

이를 통하여 사용자는

  • 검색 및 집계 기능을 통하여 손쉽게 ES의 데이터 조작 가능
  • 시각화 도구를 이용하여 DashBoard 및 Canvas 생성 가능
  • 다양한 형태(json, url)로 저장 및 Export 가능 

 

 

 

 

4. Beats

Logstash는 데이터 수집기로 좋긴 하나, 다양한 기능을 제공하기 때문에 상황에 따라 너무 무거울 수 있다.

-> 가볍게 데이터만 수집할 수 있는 Beats를 만들어냄

 

즉, Beat는 지정된 위치의 Log file 만 읽고, logstash 혹은 ES로 전달해주는 역할만 수행한다.

따라서 가공(필터)에 대한 역할을 잘 수행해지 않음(가공을 할 순 있다.)

 

Logstash와 Beats 비교

Logstash
  • 풍부한 기능을 제공(filter 및 변환)
  • Data 가공이 필요한 경우에 사용

Beats

  • resource(CPU & RAM) 적게 소모 
  • Elasticsearch로 직접 전달할 경우에 유용
    • 별도의 분석이 필요 없는 경우
    • Log 자체가 json 형식이고, 가공할 필요가 없을 경우



 

case 1

Beat는 log 파일을 읽기만 하고 별도의 가공 X

로그 파일의 format이 달라진다면?

- parsing의 역할을 하는 Logstash의 설정을 변경해주면 된다. 

따라서 이러한 경우는 beat 와 logstash의 역할을 분명히 나누었다고 볼 수 있다.

-> 확장성/효율성 측면에서 좋다.


case2

beat에서 json 형태의 문서로 가공

장점  : 빠르게 ES로 로그 전송 및 수집 가능 
단점 :  log file format이 변경되었을 때, 모든 application 서버의 beat의 설정을 변경해야 함

 

+ X-Pack

Elastic Stack에 대하여 여러가지 확장 가능성을 제공

 

Security - 인증기능, 사용자 관리, 노드, http elasticsearch 클라이언트 간의 통신 트래픽 보호등을 하며,

                    field / document 수준까지 데이터를 보호 / Audit Log(감시로그) 제공 

 

Altering -  데이터 변경사항이 있다면, 사용자에게 알림 제공

 

Monitoring - ELK Stack 의 상태를 지속적으로 체크하는 기능

 

Reporting -  PDF 형식의 주기적인 보고서를 생성할 수 있습니다. 또한 email에도 전송이 가능

 

Graph -  데이터 시각화를 그래프로써 표현

 

Machine Learning -  데이터의 흐름 및 주시성 등을 자동으로 실시간 모니터링하여 문제를 식별하고 이상값 탐지등 근본                                        원인을 분석

 

 

 

+ Elastic Cloud

 

최신 버전의 Elasticsearch 및 Kibana를 사용하여 Amazon AWS에서 실행되는 보안 클러스터를 배포하고 관리

 

 

 

 

 

 

 


출처:

'ElasticSearch' 카테고리의 다른 글

Elastic Stack에서 한국어 NLP사용  (0) 2022.06.02
조건문 사용(If Else 문)  (0) 2022.04.29
매일 ES에 있는 data를 CSV파일로 만들기  (0) 2022.04.26

상당수 코드 기반은 전적으로 오류처리 코드에 좌우되기 때문에 깨끗한 코드와 오류처리는 연관성이 있다.

문제점: 
1. 여기저기 흩어진 오류 처리 코드로 인해 실제 코드가 하는 일을  파악하기가 거의 불가능할 수  있다.
            2. 오류 처리 코드로 인해 프로그램의 논리를 이해하기 어렵다면 깨끗한 코드라 할 수 없다.

이 장에서는 오류를 처리하는 기법과 고려사항을 몇 가지 소개하고자 한다.

해당 포스팅 내용은 책 'Clean Code - Robert C. Martin' 출처로 합니다.

 

1. 오류 코드보다 예외를 사용하라

 

오류 플래그를 설정하거나 호출자에게 오류코드를 반환하는 방법

public class DeviceController {
 ...
	public void sendShutDown() {
    	DeviceHandle handle = getHandle(DEV1);
        //디바이스 상태를 점검한다. 
        
        if(handle != DeviceHandle.INVALID) {
        //레코드 필드에 디바이스 상태를 저장한다.
        	retrieveDeviceRecord(handle);
            
        //디바이스가 일시정지 상태가 아니라면 종료한다.
        	if(record.getStatus() != DEVICE_SUSPENDED) {
            	pauseDevice(handle);
                clearDeviceWorkQueue(handle);
                closeDevice(handle);
             } else {
            	logger.log("Device suspended. Unable to shut down");
             }
        } else {
        	logger.log("Invalid handle for: " + DEV1.toString());
        }
    }
    
    ...
}

위와 같은 방법을 사용하는 경우  몇 가지 문제가 발생한다.

  1. 호출자 코드가 복잡해진다. (함수를 호출한 즉시 오류를 확인해야 하기 때문)
  2. 오류 단계를 확인하는 것을 잊어버리기 쉽다.

 

해결:  코드에서 오류가 발생할 때, 예외 사용

public class DeviceController {
...

	public void sendShutDown() {
		try {
        	tryToShutDown();
        } catch(DeviceShutDownError e) {
        	logger.log(e);
          }
    }
    
    private void tryToShutDown() throws DeviceShutDownError {
		DeviceHandle handle = getHandle(DEV1);
        DeviceHandle record = retrieveDeviceRecord(handle);
        
        pauseDevice(handle);
        clearDeviceWorkQueue(handle);
        closeDevice(handle);
    }
    
    private DeviceHandle getHandle(DeviceID id){
    ...
    
    	throw new DeviceShutDownError("Invalid handle for:" + id.toString());
    	...
    }
    
    ...
}

장점: 디바이스를 처리하는 알고리즘과 오류를 처리하는 알고리즘을 분리함으로써 호출자 코드가 간단해진다.

 

 

2. Try-Catch-Finally 문부터 작성하라

try-catch-finally 문에서 try 블록에 들어가는 코드를 실행하면 어느 시점이든 실행이 중단된 후 catch 블록으로 넘어갈 수 있다.

1. try 블록은 트랜잭션과 비슷해서 try 블록에서 무슨 일이 생기든지 catch 블록에서 프로그램 상태를 일관성 있게 유지 가능
(cf. 트랜잭션: 쪼갤 수 없는 업무의 최소 단위)
2. 블록에서 무슨 일이 생기든지 호출자가 기대하는 상태를 정의하기 쉬움

 

다음은 파일이 없으면 예외를 던지는지 알아보는 단위 테스트다.

1. 단위 테스트 만들기
@Test(expected = StorageException.class)
public void retrieveSectionShouldThrowOnInvalidFileName() {
	sectionStore.retriveSection("Invalid - file");
}​

2. 단위 테스트에 맞춰 코드 구현
public List<RecordedGrip> retrieveSection(String sectionName) {
 //실제로 구현할 때까지 비어 있는 더미를 반환하다.
 	return new ArrayList<RecordedGrip>() ;
}​

하지만 코드가 예외를 던지지 않으므로 단위 테스트 실패

3. 잘못된 파일 접근을 시도하게 구현을 변경

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>();
}

 
4. 리팩토링이 가능

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>();
}

 (catch 블록에서 예외 유형을 좁혀 FileNotFoundException을 잡아냄)

 

위와 같은 방식은 try-catch 구조로 범위를 정의하고, TDD를 사용해 필요한 나머지 논리를 추가하는 방식이다.

(강제로 예외를 일으키는 테스트 케이스를 작성한 후, 테스트를 통과하게 코드를 작성하는 방법)

 

->try 블록의 트랜잭션 범위부터 구현하게 되므로 범위 내에서 트랜잭션 본질을 유지하기 쉬워짐 

 

(cf. TDD(테스트 주도 개발) : 반복 테스트를 이용한 소프트웨어 방법론으로, 작은 단위 테스트 케이스를 작성하고 이를 통과하는 코드를 추가하는 단계를 반복하여 구현하는 방법)

 

 

3. 미확인 예외(Unchecked Exception)를 사용하라

지금은 안정적인 소프트웨어를 제작하는 요소로 확인된 예외(Checked Exception)가 반드시 필요하지 않다.

먼저 확인된 예외와 미확인 예외의 차이는 아래 표로 정리하였다.

 

  Checked Exception Unchecked Exception
확인시점 컴파일 런타임
처리 여부 반드시 처리 명시적인 처리를 강제하지 X
트랜잭션 처리  roll-back 하지 X  roll-back 
예시 IOException, ClassNotFoundException NullPointerException, ArithmeticException

(cf. rollback: 업데이트에서 오류가 발생했을 때, 이전 상태로 되돌리는 것(오류 발생 전으로 되돌리는것))

 

대규모 시스템에서 최하위 함수를 변경해 새로운 오류를 던진다고 가정하자

확인된 오류를 던진다면 함수는 함수는 선언부에 throws 절을 추가해야 한다.

이로 인해 변경한 함수를 호출하는 모든 함수가 1) catch 블록에서 새로운 예외를 처리하거나 2) 선언부에 throw 절을 추가해야 한다.

-> 최하위 단계에서 최상위 단계까지 연쇄적인 수정이 일어난다.

 

확인된 예외의 단점:

1. 하위 단계에서 코드를 변경하면 상위 단계 메서드 선언부를 전부 고쳐야 한다. 

2. throws 경로에 위치하는 모든 함수가 최하위 함수에서 던지는 예외를 알아야 하므로 캡슐화가 깨진다.

 

확인된 예외의 장점:

아주 중요한 라이브러리를 작성할 때 모든 예외를 잡아야 한다.

그래서 우리는 확인된 오류를 치르는 비용에 상응하는 이익을 제공하는지 따져봐야 한다.
확인된 예외는 OCP 원칙을 위반하기 때문이다.
하지만 일반적인 애플리케이션은 의존성이라는 비용이 이익보다 크다.

 

 

4. 예외에 의미를 제공하라

예외를 던질 때 전후 상항을 충분히 덧붙여야 한다. 그러면 오류가 발생한 원인과 위치를 찾기 쉬워짐

 

자바는 모든 예외에 호출 스택을 제공하지만 실패한 코드의 의도를 파악하기엔 부족

해결: 오류 메시지에 정보(실패한 연산 이름, 실패 유형 등)를 담아 예외와 함께 던진다.

 

 

5. 호출자를 고려해 예외 클래스를 정의하라

오류를 분류하는 방법은 많다.

(오류가 발생한 위치, 오류가 발생한 컴포넌트, 오류가 발생한 유형)

하지만 프로그래머에거 가장 중요한 관심사는 오류를 잡아내는 방법이 되어야 한다.

 

잘못된 경우) 형편없이 오류를 분류한 케이스(외부 라이브러리가 던질 예외를 호출자가 모두 잡아냄)

ACMEPort port = new ACMEPort(12);

 try {
     port.open();
 } catch (DeviceResponseException e) {
     reportPortError(e);
     logger.log("Device response exception", e);
 } catch (ATM1212UnlockedException e) {
     reportPortError(e);
     logger.log("Unlock exception", e);
 } catch (GMXError e) {
     reportPortError(e);
     logger.log("Device response exception");
 } finally {
     ...
 }

 

해결) 호출하는 라이브러리 API를 감싸서 예외 유형 하나를 반환

LocalPort port = new LocalPort(12);
try {
  port.open();
} catch (PortDeviceFailure e) {
  reportError(e);
  logger.log(e.getMessage(), e);
} finally {
  ...
}

public class LocalPort {
  private ACMEPort innerPort;
  
  public LocalPort(int portNumber) {
    innerPort = new ACMEPort(portNumber);
  }
  
  public void open() {
    try{
      innerPort.open();
    } catch (DeviceResponseException e) {
      throw new PortDeviceFailure(e);
    } catch (ATM1212UnlockedException e) {
      throw new PortDeviceFailure(e);
    } catch (GMXError e) {
      throw new PortDeviceFailure(e);
    }
  }
  
  ...
}

위의 코드는 LocalPort 클래스가 ACMEPort 클래스가 던지는 예외를 잡아 변환하는 래퍼클래스이다.

 

래퍼클래스 기법의 장점)

 

1. 외부 API를 감싸기 때문에 외부 라이브러리와 프로그램 사이에서 의존성이 크게 줄어듬

2. 다른 라이브러리로 갈아타도 비용이 적음

3. 외부 API를 호출하는 대신 테스트 코드를 넣어주는 방법으로 프로그램을 테스트하기 쉬워짐

4. 외부 API 설계 방식에 의존하지 않아도 됨

 

6. 정상 흐름을 정의해라

중단이 적합하지 않은 때도 있다. 이때, "특수 사례 패턴"으로 클래스를 만들거나 객체를 조작해 특수사례를 처리한다.

->  클라이언트 코드가 예외적인 상황을 처리할 필요X

 

총계를 계산하는 코드

try {
     MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
     m_total += expenses.getTotal();
 } catch(MealExpencesNotFound e) {
     m_total += getMealPerDiem();
 }

 클래스나 객체가 예외적인 상황을 캡슐화 해서 처리한다.

MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
	m_total += expenses.getTotal();

ExpenseReportDAO를 고쳐 언제나 MealExpense 객체를 반환하게 한다.
청구한 식비가 없다면 일일 기본 식비를 반환하는 MealExpense 객체를 반환한다.

(getTotal() 메소드에 예외 시 처리를 넣어 클라이언트 코드를 간결하게 처리)

public class PerDiemMealExpenses implements MealExpenses {
	public int getTotal() {
		// 기본값으로 일일 기본 식비를 반환한다.
	}
}

 

7. null을 반환하지 마라

null을 반환하고 이를 if(object != 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을 체크할 의무를 떠넘김
  • NullPointerException 의 발생 위험이 있음
  • null확인이 너무 많아짐

 

해결)

  • null 대신 예외를 던지거나 특수 사례 객체를 반환하라
  • 사용하려는 외부 API가 null을 반환한다면 Wrapper 를 구현해 예외를 던지거나 특수 사례 객체를 반환하라

 

문제) null을 전달하는 코드

List<Employee> employees = getEmployees();
if (employees != null) {
	for(Employee e : employees) {
		totalPay += e.getPay();
	}
}

 

해결 1) getEmployees를 변경해 null대신 빈 리스트를 반환

List<Employee> employees = getEmployees();
for(Employee e : employees) {
	totalPay += e.getPay();
}

 

해결2) 특수 사례 객체(자바의 Collections.emptyList()) 사용

public List<Employee> getEmployees() {
	if ( ...직원이 없다면... ) {
		return Collections.emptyList();
	}
}

 

 

8. null을 전달하지 마라

메서드에서 null을 반환하는 방식도 나쁘지만 null을 전달하는 방식은 더 나쁘다.

인수로 null을 전달하는 것을 해결하기 위한 대안이 2가지가 있다.

  • 예외처리
  • assert 문을 사용

하지만 애초에 null을 전달하는 경우는 금지하는 것이 바람직하다.

왜냐하면 대부분의 프로그래밍 언어는 호출자가 실수로 넘기는 null을 적절히 처리할 방법 X

 

결론: 오류처리를 프로그램 논리와 분리시키야 코드 유지보수성이 높아지고 결국 깨끗한 코드를 만들 수 있다.

'Clean Code' 카테고리의 다른 글

[Clean Code] Chapter 18 동시성 2  (0) 2022.06.02
[Clean Code] Chapter 13 동시성  (0) 2022.03.22
[Clean Code] Chapter 12 창발성  (0) 2022.03.21
Chapter 2 의미있는 이름  (0) 2022.01.13

이름을 잘짓는 규칙을 몇가지 소개하려 한다.

해당 포스팅 내용은 책 'Clean Code - Robert C. Martin' 출처로 합니다.

 

1. 의도를 분명히 밝혀라   

 좋은 이름을 지으려면 시간이 걸리지만 그로 인해 절약하는 시간이 훨씬 더 많기 때문에 좋은 이름을 짓도록 노력해야 한다. 

이때, 변수나 함수 클래스 이름을 지을 때 아래와 같은 질문에 답을 할 수 있어야 한다.

▶ 변수(혹은 함수나 클래스)의 존재 이유가 무엇인가?

▶ 수행기능은 무엇인가?

▶ 사용방법은 어떻게 되는가?

 

만약 따로 많은 주석이 필요하다면 의도를 분명히 드러내지 못한 것이다.

-> 코드의 단순성보다 중요한 것은 코드의 함축성이다.


 2. 그릇된 정보를 피하라 

 

 나름대로 널리 쓰이는 의미가 있는 단어를 다른 의미로 사용하면 안 된다.

 예를 들어,

  - hp, aix, sco는 유닉스 플랫폼이나 유닉스 변종을 가리키는 의미이다. 만약 직각삼각형의 빗변(hypotenuse)을 구현할 때 hp라는 변수  를 사용하면 독자에게 그릇된 정보를 제공할 수 있다.

 - 여러 계정을 그룹으로 묶을 때, 실제 List가 아니라면, accountList라 명명하지 않는다. 프로그래머에게 List라는 단어는 특수한 의미이다.

 

 서로 흡사한 이름을 사용하지 않도록 주의해야 한다.

 유사한 개념은 유사한 표기법을 사용한다. 표기된 이름은 일종의 정보이기 때문에 일관성이 떨어지는 표기법은 그릇된 정보다.


3. 의미있게 구분하라

 연속된 숫자를 덧붙이거나 불용어(의미가 없는 단어)를  추가하는 방식은 적절하지 못하다.

 - 연속적인 숫자를 덧붙인 이름(a1, a2, ..., aN) 

 - Info나 Data(의미가 불분명한 불용어)

 - 변수 이름에 variable을 붙이거나 표 이름에 table을 쓰는 경우 

 이런 이름은 그릇된 정보를 제공하는 이름도 아니며, 아무런 정보도 제공하지 못한다.

의미가 분명히 다를 경우 접두어를 사용해도 무방하지만 어떤 변수가 존재한다고 해서 해당변수에 접두어를 붙여서 이름을 만들면 안 된다.

-> 읽는 사람이 차이를 알도록 이름을 지어야 한다.


4. 발음하기 쉬운 이름을 사용하라

 보통 사람들은 단어에 능숙하며, 우리 두뇌의 상당 부분은 단어라는 개념만 전적으로 처리한다.

 프로그래밍은 사회활동이기 때문에 다른 사람과 쉽게 소통하기 위해 발음하기 어려운 이름은 피하도록 한다.


5. 검색하기 쉬운 이름을 사용하라

 문자 하나를 사용하는 이름과 상수는 텍스트 코드에서 쉽게 눈에 띄지 않는 문제점이 있다.

 만약 문자 하나(혹은 상수)를 사용하는 이름에 버그가 있다면 검색으로 찾아내지 못한다.

 -> 간단한 메서드에서 로컬변수만 한 문자를 사용한다, 이름 길이는 범위 크기에 비례해야 한다.


6. 인코딩을 피하라

 이름에 인코딩할 정보는 많기 때문에 유형이나 범위 정보까지 인코딩에 넣으면 이름을 해독하기 어려운 문제점이 있다.

 아래는 우리가 피해야할 이름이다.

 - 헝가리식 표기법과 기타 인코딩 방식

 - 멤버 변수 접두어(m_)

 

하지만 인코딩이 필요한 경우도 있는데 그 중 하나가 인터페이스 클래스와 구현 클래스이다.

둘 중 하나를 인코딩해야 한다면, 인터페이스 클래스 이름에 접두어 'I'를 붙이는 것보다 구현 클래스 이름에 "Impl"을 붙이것이 낫다.


7. 자신의 기억력을 자랑하지 마라

 독자가 코드를 읽으면서 변수 이름을 자신이 아는 이름으로 변환해야 한다면 그 변수 이름은 좋지 않은 이름이다. 

 이는, 일반적으로 문제 영역이나 해법 영역에서 사용하지 않는 이름을 선택했기 때문에 생기는 문제다.

 전통적으로 루프에서 반복 횟수 변수는 한 글자를 사용하는 경우를 제외하고는 한 글자를 변수로 사용하는 것은 적절하지 않다.

-> 이름은 명료해야 한다.

 클래스 이름 (혹은 객체 이름): 명사나 명사구 ( ex. Customer, WIkiPage,...)

 메서드 이름: 동사나 동사구

 (postPayment, deletePage,...)

 (접근자, 변경자, 조건자는 javabean 표준에 따라 값 앞에 get, set, is를 붙인다.) 


8. 기발한 이름은 피하라

 특정 문화에서만 사용하는 농담을 이름으로 하는 것은 피하는 것이 좋다.

 이는 특정 소수만 알 수 있게 된다.

 - kill() 대신에 whack()을 사용하는 경우

 - Abort() 대신에 eatMyShort()를 사용하는 경우

 -> 의도를 분명하고 솔직하게 표현해야 한다.


9. 한 개념에 한 단어를 사용하라

 추상적인 개념 하나에 단어 하나를 선택해 이를 고수해야 한다.

 만약 추상적인 개념 하나에 단어 하나를 선택하지 않는다면 아래와 같은 문제가 생긴다.

 - 똑같은 메서드를 클래스 마다 fetch, retrieve, get으로 이름을 정한 경우

 - 동일 코드 기반에서 controller, manager, driver를 섞어서 쓰는 경우

 

 -> 일관성 있는 어휘를 사용해야 한다.


10. 말장난을 하지 마라

 한 단어를 두 가지 목적으로 사용하면 안 된다. 다른 개념에 같은 단어를 사용한다면 그것은 말장난에 불과하다.

 (ex. add메서드가 기존 값 두 개를 더하거나 이어서 새로운 값을 만들 때, 집합에 같은 값 하나를 추가하는 메서드 이름을 add로 하는 경우, 이는 기존의 add 메서드와 다른 맥락이기 때문에 insert나 append라는 이름을 사용하는 것이 좋다)

 

-> 프로그래머는 코드를 최대한 이해하기 쉽게 짜야한다. (대충 훑어봐도 이해할 코드가 코드 작성의 목표)


11. 해법 영역에서 가져온 이름을 사용하라

 코드를 읽을 사람도 프로그래머라는 사실을 명심해야 한다. 전산 용어, 알고리즘 이름, 패턴 이름, 수학 용어 등을 사용하는 것은 괜찮다.

 하지만, 모든 이름을 문제 영역에서 가져오는 정책은 현명하지 못하다.

 -> 기술 개념에는 기술 이름이 가장 적합한 선택이다.


12. 문제 영역에서 가져온 이름을 사용하라

 적절한 '프로그래머 용어'가 없다면 문제영역에서 이름을 가져온다. 

 그러면 코드를 보수하는 프로그래머가 분야 전문가에게 의미를 물어 파악할 수 있게 된다.

 우수한 프로그래머는 문제영역과 해법영역을 구분할 줄 알아야 한다.

 -> 문제 영역 개념과 관련이 깊은 코드는 문제 영역에서 이름을 가져와야 한다.


13. 의미 있는 맥락을 추가하라

 대다수의 이름은 스스로 의미가 분명한 이름이 없기 때문에 클래스, 함수, 이름 공간에 넣어 맥락을 부여한다.

 만약 모든 방법이 실패할 경우, 마지막 수단으로 접두어를 붙인다.

 (ex. firstName, lastName, street, houseNumber, city, state, zipcode라는 변수가 같이 있다면 주소와 관련이 있는 변수를 알 수   있지만, 어느 메서드가 state 변수 하나만 사용하면 변수 state가 주소 일부라는 사실을 알기 어렵다. 이때 addr이라는 접두어를 모든  변   수명에 추가한다면 맥락이 분명해진다. 하지만 더 좋은 방법은 Address라는 클래스를 생성하여 관련 변수들을 해당 클래스에 넣는 것이   다.)


14. 불필요한 맥락을 없애라 

 예를 들어, 고급 휘발유 충전소(Gas Station Deluxe)라는 애플리케이션을 짠다고 가정해보자.

 모든 클래스 이름을 GSD로 시작한다면 IDE에서 G를 입력하고 자동완성 키를 누르면 모든 클래스가 열거된다.

 이로 인해 IDE를 제대로 사용할 수 없게 된다.

 하지만 일반적으로 의미가 분명한 경우, 짧은 이름이 긴 이름보다 좋다.


마치면서

대부분의 사람들은 자신이 짠 클래스 이름과 메서드 이름을 모두 암기하지 못한다. 그렇기 때문에 암기는 도구에게 맡기고, 우리는 문장이나 문단처럼 읽히는 코드나 적어도 표나 자료 구조처럼 읽히는 코드를 짜는데 집중해야 한다.

즉, 코드 가독성이 높아지도록 코드를 짜는데 노력해야 한다. 

'Clean Code' 카테고리의 다른 글

[Clean Code] Chapter 18 동시성 2  (0) 2022.06.02
[Clean Code] Chapter 13 동시성  (0) 2022.03.22
[Clean Code] Chapter 12 창발성  (0) 2022.03.21
Chapter 7 오류 처리  (0) 2022.02.09

+ Recent posts