반응형

시스템에 들어가는 모든 소프트웨어를 직접 개발하는 경우는 드물다

이 장에서는 소프트웨어 경계를 깔끔하게 처리하는 기법과 기교를 살펴본다.

 

외부 코드 사용하기


더 많은 환경에서 돌아가야 더 많은 고객이 구매하니까 패키지 제공자나 프레임워크 제공자는 적용성을 최대한 넓히려 애쓴다. 반면 사용자는 자신의 요구에 집중하는 인터페이스를 바란다. 

이런 차이로 시스템 경계에 문제가 생길 소지가 많다.

 

ex) java.util.Map

 

설명이 완전히 java적인 내용이긴 한데.. 뭘 의미하고 있는지를 알아두면 좋을것 같다.

 

Map은 다양한 인터페이스로 수많은 기능을 제공한다.

이러한 기능성과 유연성은 확실히 유용하지만

Map을 그대로 넘긴다는 뜻은 예를들어 Map 내용을 지우는 clear()함수를 누구나 지울 권한을 가질 수 있다는 뜻과 같다.

 

Map sensors = new HashMap(); 
Sensor s = (Sensor) sensors.get(sensorId);

또한 위와 같이 Map을 올바른 유형의 Object로 변환할 책임은 Map을 구현한 곳이 아닌 실제로 사용하는 클라이언트 쪽에 있게 된다.

또한 의도가 드러나지 않는다. 대신 다음과 같이 Generics를 사용하면 코드 가독성이 크게 높아진다.

Map<String, Sensor> sensors = new HashMap<Sensor>();
Sensor s = sensors.get(sensorId);

하지만 여전히 사용자가 필요하지 않은 기능까지 넘기는것이 좋지 않다.

 

다음과 같이 제네릭스 사용 여부라던지 경계 인터페이스인 Map을 Sensor 클래스 안에 숨겨보자.

public class Sensors { 
	private Map sensors = new HashMap();
    
	public Sensor getById(String id) { 
	return (Sensor) sensors.get(id);
	} 
}

 

그러면 Map 인터페이스가 변하더라도 Sensor 클래스 안에서 객체 유형을 관리하고 변환할 수 있게 된다.

또한, 필요한 인터페이스만 제공할 수 있게 된다.

 

경계 살피고 익히기

 

외부 코드를 익히기는 어렵다. 외부 코드를 통합하기도 어렵다. 만약 곧바로 우리쪽 코드를 작성해 외부 코드를 호출하는 대신 먼저 간단한 테스트 케이스를 작성해 외부 코드를 익히면 어떨까?

 

학습 테스트 는 프로그램에서 사용하려는 방식대로 외부 API를 호출한다. 통제된 환경에서 API를 제대로 이해하는지를 확인하는 셈이다. 학습 테스트는 API를 사용하려는 목적에 초점을 맞춘다.

 

학습 테스트는 공짜 이상이다.

 

학습 테스트는 이해도를 높여주는 정확한 실험이다.

학습 테스트에 드는 비용은 없고 필요한 지식만 확보하는 쉬운 방식이다.

 

패키지가 새로운 버젼이 나오면 학습 테스트를 돌려 차이가 있는지 확인한다.

새 버젼이 호환되지 않으면 바로 학습 테스트가 밝혀낸다.

 

이런 테스트가 존재한다면 어느 부분이 호환성 문제가 생기는지 빨리 밝혀내고 새 버젼으로 빨리 이전하기 쉬워진다.

 

아직 존재하지 않는 코드를 사용하기

경계와 관련해 또 다른 유형은 아는 코드와 모르는 코드를 분리하는 경계다.

 

ex) 저쪽 팀이 아직 API를 설계하지 않았으므로 구체적인 방법은 모를때
구현을 미루고 이쪽 코드를 진행하기 위해 자체적으로 인터페이스를 정의한다.

 

어뎁터 패턴을 이용해서 다른 팀이 개발하고 있는 코드를 분리하고, API 사용을 캡슐화해 API 바뀔때 수정할 코드를 한곳으로 모은다.

(부가적으로 테스트도 쉬워짐)

 

깨끗한 경계

 

통제하지 못하는 코드(오픈소스나 외부 라이브러리 등)를 사용할때는 주의해야한다.

경계에 위치하는 코드는 깔끔히 분리한다.

 

이쪽에서 상대 코드를 세세히 알 필요는 없다. 외부패키지에 의존하지 말고 통제가 가능한 우리 코드에 의존한다.

외부 패키지를 호출하는 코드를 가능한 줄여 경계를 관리하자.

 

Adapter pattern을 사용하던지 위에서 보았던것처럼 새로운 클래스로 경계를 감싸서 우리가 인터페이스를 만들어 제어하자.

 

 

핵심 

 

-> 외부 라이브러리와 맞닿는 부분은 직접 쓰지말고 인터페이스 같은 방식으로 분리하고, 주도적으로 제어

이 파트는 좀 심오한 내용인것 같다.. 

일단 '분리' 자체에는 동의한다.

반응형

'독서 > cleancode' 카테고리의 다른 글

cleancode - class  (0) 2022.02.26
cleancode - unit test  (0) 2022.01.25
cleancode - 오류처리  (0) 2022.01.22
cleancode - 객체와 자료구조  (0) 2022.01.21
clean code - 주석 & 형식 맞추기  (0) 2022.01.20
반응형

 

캐시란?

 

자주 사용하는 데이터나 값을 미리 복사해 놓는 임시 장소를 가리킨다. 캐시는 저장 공간이 작고 비용이 비싼 대신 빠른 성능을 제공한다. 

 

Cache는 아래와 같은 경우에 사용을 고려하면 좋다.

  • 접근 시간에 비해 원래 데이터를 접근하는 시간이 오래 걸리는 경우(서버의 균일한 API 데이터)
  • 반복적으로 동일한 결과를 돌려주는 경우(이미지나 썸네일 등)

캐시는 컴퓨터 구조뿐만 아니라 네트워크, DB등 여러곳에서 자주 사용하는 개념이다.

이글에서는 CPU 내부의 캐시에 대해 간략하게 알아본다.

 

CPU 캐시 란

 

cpu의 내부 캐시란 CPU와 메인 저장 장치(하드디스크)간 속도의 모순을 해결하기 위해서 사용하는 것이다.

 

CPU 는 종종 동일한 데이터를 반복적으로 처리하고, 실행하는데(지역성) 그때마다 메모리에서 받아와 처리한다면 

비교적 느린 하드디스크에서 데이터를 받아올때마다 CPU는 기다리는 오버헤드가 발생하게 된다.

 

그래서 CPU 내부에 일종의 저장 장소(SRAM)를 가지고 캐싱해놓게 되는데 그것을 캐시라 한다.

 

L1, L2, L3 캐시 메모리등이 있는데

숫자가 작을수록 작고 비싸고, 빠르고 CPU 내부에서 가장 가까운 곳에 위치해 있다고 보면 된다.

 

그리고 cache를 제어하는 방식에 대해 간단히 알아보자.

 

write-Through

CPU가 데이터를 사용하면 캐시에 저장되게 되는데, 데이터가 캐시 됨과 동시에 주기억장치 또는 디스크로 기입되는 방식을 지원하는 구조의 캐시이다. 즉, 캐시와 메모리 둘다에 업데이트를 해버리는 방식이다.

 

다만 업데이트때마다 직접 하면 cost가 비싸므로 

Write buffer라는 구조를 사용해서 CPU 프로세서가 직접 Write 명령을 수행하지 않아 대기하는 시간을 줄여주는 방식으로 작동한다고 한다. (이건 write Back에도 사용 가능)

 

write-Back

 데이터를 쓸 때 메모리에는 쓰지 않고 캐시에만 업데이트를 하다가 필요할 때에만 주기억장치나 보조기억장치에 기록하는 방법이다.

 

그럼 캐싱을 사용하면 안될때는 언제일까?

C언어, Java에서는 volatile 명령어로 캐싱을 사용하지 않고 메인메모리에서 접근해서 가져오도록 사용 가능하다.

volatile int num1 = 10;    // 변수를 최적화에서 제외하여 항상 메모리에 접근하도록 만듦
public class SharedObject {
    public volatile int counter = 0;
}

 

그럼 이것을 언제 사용할까?

캐시는 지금까지 좋다고 글을 썼는데 왜 캐시를 안쓰고 굳이 메인 메모리에서 값을 가져올까?

그 답은 아래 그림처럼 멀티 CPU 상황일경우 임계영역인 데이터의 동기화를 위해 쓴다고 볼 수 있다.

 

혹은 임베디드 프로그래밍에서 인터럽트 내부에서 메모리의 어떤 값이 수정되었는데 CPU 내부의 캐시는 수정되지 않았을때를 방지하려고 volatile 예약자를 쓴다. 

 

읽어보면 좋을 글)

https://blog.naver.com/cjsksk3113/222253156868

 

캐시 일관성(Cache Coherence)과 캐시 속성(Cacheable / Non Cacheable)

캐시 메모리 요즘의 CPU 주기억장치 메모리(Main Memory)는 거의 100% SDRAM으로 구성된다. SD...

blog.naver.com

 

reference 

 

https://itgall.com/hardware/232948

 

CPU 캐시가 L1, L2 및 L3으로 나뉘는 이유에 대해 알아보자

캐시라는 용어는 누구나 들어봤을 것입니다. 사실 캐시의 의미는 매우 광범위합니다. 컴퓨터에서 가장 큰 캐시는 메모리 스틱으로 구현할 수 있고 그래픽 카드의 비디오 메모리는 그래픽 칩에

itgall.com

 

https://mangkyu.tistory.com/69

 

[Server] Cache(캐시)란?

1. 캐시(Cache)란? [ Cache ] Cache란 자주 사용하는 데이터나 값을 미리 복사해 놓는 임시 장소를 가리킨다. 아래와 같은 저장공간 계층 구조에서 확인할 수 있듯이, 캐시는 저장 공간이 작고 비용이

mangkyu.tistory.com

https://nesoy.github.io/articles/2018-06/Java-volatile

 

Java volatile이란?

 

nesoy.github.io

https://blog.naver.com/PostView.nhn?blogId=cjsksk3113&logNo=222282586535 

 

캐시 메모리의 쓰기 정책 : Write-Through, Write-Back

캐시 메모리는 CPU 프로세서의 동작을 돕기 위한 임시 메모리 저장소이다. 따라서 CPU 프로세서가 캐...

blog.naver.com

 

반응형

'CS > 컴퓨터구조' 카테고리의 다른 글

[컴퓨터구조] 하버드 구조와 폰 노이만 구조  (0) 2022.01.26
반응형

3-way handshake와 4-way handshake는 TCP 프로토콜에서 연결을 시작/종료할때 쓰는 방식이다.

우선 TCP부터 알아보자.

 

TCP

TCP란 연결 지향적 프로토콜로 데이터패킷을 주고받을때 신뢰성을 보장한다.

 

그외 특징으로는

  • 흐름 제어, 혼잡 제어, 오류 제어
  • UDP보다 느린 대신 OSI 4계층(transport)에서 신뢰성 보장
  • 파일을 분할해서 전송(MSS)
  • 연결형 서비스로 가상 회선 방식을 제공

신뢰성을 보장하는 방식으로는 패킷을 보낸 후, 수신 확인(ACK)를 받는다.

제한시간내에 ACK가 오지 않는다면 패킷이 네트워크 중간에 손실되었다고 가정하고 다시 보낸다.

따라서 해당 방식때문에 신뢰성을 보장하지만 느리다.

(혼잡제어나 패킷을 보내는 방식은  흥미로운 부분이라 나중 글을 관련 부분으로 더 쓸 예정)

 

3-way handshake

연결하기 위해 두 장치의 논리적 접속을 성립하기 위해 사용하는 연결 확인 방식이다.

3번의 확인을 거친다고 해서 3 way handshake라 부른다

 

tmi) 4계층(transport)는 host 프로세스와 guest 프로세스 간 양 호스트 종단의 논리적 통신을 보장하고,

3계층(network)는 host와 guest 간 논리적 통신을 보장한다.

 

 

접속 시도시 client는 SYN 패킷을 보내 server에 접속 요청을 보낸다.

서버는 SYN 패킷을 받으면 접속이 가능하다는 의미로 client에세 SYN과 ACK 패킷을 보낸다.

client는 서버에세 SYN+ACK 패킷을 받았다면 이제 통신을 시작했다는 의미로 ACK를 보내고,

통신을 시작한다.

 

SYN (synchronize sequence numbers) - 연결 확인을 위해 보내는 무작위의 숫자값

ACK (acknowledgements) - Client 혹은 Server로부터 받은 SYN에 1을 더해 SYN을 잘 받았다는 ACK

ISN (Initial sequence numbers) - Client와 Server가 각각 처음으로 생성한 SYN

 

- SYN의 값에 무작위 수를 사용하는 이유?

  • Connection을 맺을 때 사용하는 포트는 유한 범위 내에서 사용하고 시간이 지남에 따라 재사용된다. 따라서 두 통신 호스트가 과거에 사용된 포트 번호 쌍을 사용할 가능성이 존재한다. 서버 측에서 패킷의 SYN을 보고 패킷을 구분하게 되는데 난수가 아닌 순차적인 숫자가 전송된다면 이전의 connection으로부터 오는 패킷으로 인식할 수 있어 이러한 문제 발생 가능성을 줄이기 위해 ISN을 무작위 난수로 사용하는 것이다.

 

4-way handshake

4-way handshake는 반대로 가상 회선 연결을 해제할때 주고 받는 확인 작업이다.

4번의 확인과정을 거친다고 하여 4 way handshake라고 부른다.

 

흔히 client가 close를 요청하고, server가 확인하고 닫는다고 알려져 있는데

서버도 close를 요청할 수 있다.

그래서 먼저 4-way handshake를 요청하는 쪽을 Active Closer, 받는 쪽을 Passive Closer라 한다.

 

과정을 요약하자면

 

Active closer가 먼저 서버를 닫고 싶다는 FIN 요청을 보내고 FIN-wait 상태에 들어간다.

그럼 Passive closer는 알았다는 ACK를 보내고, close-wait 상태에 들어간다.

그리고 Passive closer는 통신을 종료할 준비가 완료되면 FIN 을 다시 Active closer에 보낸다.

Active Closer는 FIN을 받았으면 time-wait 과정에 들어가고 ACK를 passive closer에게 보낸다. 

passive closer는 ACK를 받으면 통신을 종료한다.

active closer는 time wait상태에서 대기하다가 통신을 종료한다.

 

 

time wait는 

 

먼저 연결을 끊는 (active closer) 쪽에 생성되는 소켓으로, 혹시 모를 패킷 전송 실패에 대비하기 위하여 존재하는 소켓이다.

time-wait가 없다면 패킷의 손실이 발생하거나 통신자 간 연결 해제가 제대로 이루어지지 않을 수 있다.

 

 

읽어보면 좋을 글)

 

3 way handshake의 과정을 악용한 SYN flooding attack(보안)

https://run-it.tistory.com/51

 

SYN Flooding이란?

안녕하세요 여러분! IT 세계에는 다양한 분야가 존재합니다. 멀고 먼 험난한 길이지만... 우리는 지금까지 네트워크 영역을 다루고 이제 보안 영역에 들어와 계속해서 순항중에 있습니다. 비록

run-it.tistory.com

 

reference

 

https://seongonion.tistory.com/74

 

[네트워크] TCP 3-way & 4-way handshake란?

TCP란 연결 지향형 프로토콜로, 연속성 있는 데이터 패킷을 주고 받을 때 사용한다. TCP 특징 전송되는 데이터의 신뢰성 보장 (흐름 제어, 혼잡 제어, 오류 제어) 파일전송에 주로 사용 가상 회선을

seongonion.tistory.com

 

https://tech.kakao.com/2016/04/21/closewait-timewait/

 

CLOSE_WAIT & TIME_WAIT 최종 분석

트래픽이 많은 웹 서비스를 운영하다보면 CPU는 여유가 있지만 웹서버가 응답을 제대로 처리하지 못하고 먹통이 되는 경우를 종종 보게 됩니다. 여러가지 이유가 있겠지만, 이 글에서는 가장 대

tech.kakao.com

 

 

반응형

'CS > 네트워크' 카테고리의 다른 글

AnyCast란?  (0) 2022.04.11
URL & URI & URN  (0) 2022.02.27
HTTP 메소드 멱등성, 안전한 메소드  (0) 2022.01.31
Pooling, Long Pooling, Streaming  (0) 2022.01.23
반응형

기존 HTTP 프로토콜은 '요청'과 '응답'으로 이루어진 Client-Server Architecture로 구성되어 있다. 

그래서 클라이언트에서 요청을 하게 되면 서버가 응답 하는 방식으로 구성되어 있는데,

 

기존 정적인 HTML 파일로 구성된 초기 웹 서비스에서는 실시간 양방향 통신을 할 필요가 없으므로

이런 반이중 통신으로 충분히 웹 홈페이지를 만들어나갈 수 있었다.

 

그런데 웹을 메신저(페이스북 등?)처럼 쓰거나 게임, PWA(웹앱)등 다양한 용도로 사용하기 시작하면서

실시간 양방향 통신 방식이 필요하게 되었는데

pooling, long pooling, streaming 방식은 기존 HTTP 프로토콜 방식에서 양방향 통신(처럼) 작동하려고

노력한 일종의 시도이다.

 

최근엔 WebSocket이라는 양방향 통신을 제공하는 새로운 프로토콜이 생겼지만

js의 socket.io는 브라우저가 만약 웹 소캣을 지원하지 않으면 pooling, long pooling, streaming 방식으로 자동 치환되서 작동한다고 하니 pooling, long pooling, streaming에 대해 알아놓으면 좋을것 같다.

 

Pooling 방식

 

 

pooling 방식은 가장 간단하다.

주기적으로 서버에 클라이언트가 AJAX request를 보내 발생하는 이벤트를 계속 체크하는 것이다.

 

주기가 짧아지면 신속하게 받아올 수 있지만, 불필요한 요청/응답 트래픽이 자주 발생한다. 

요청에 대한 서버 부담이 크지 않거나 실시간 메시지 전달이 크게 중요하지 않은 서비스에 적합하다. 

 

Long Polling 방식 

 

Long Polling 방식은 실시간 메시지 전달이 중요하지만 서버의 상태 변경이 빈번히 발생하지는 않는 서비스에 적합하다.

 

Long-Polling 방식은 서버에 요청을 보내고 서버 이벤트가 발생할 때까지 연결을 유지한다. 이 상황에서 이벤트가 발생하면 응답을 받아서 처리하고 그 즉시 또 다른 이벤트를 받기 위해 연결을 맺는 방식이다. 

 

Long Polling 방식은 보통 서버 응답을 무한정 기다리는 게 아니라 특정 시간이 지나면 해당 요청/응답 트랜잭션을 완료하고 새로이 요청하는 방식으로 구현한다. (이것은 스트리밍 방식도 동일하다)

 

만약 상태변경이 빈번하고, 그것을 client에 계속 알려주게 된다면 상태 변경 이벤트가 발생할때마다 polling 방식보다 과부하가 더 많이 발생할수도 있다.

 

streaming 방식

스트리밍 방식은 한 번 요청 후 응답을 완료하지 않고 해당 응답 스트림으로 필요할 때마다 데이터를 전송하는 방식이다.

 

long polling 방식과 다른 점은 long pooling은 이벤트를 받으면 종료하고 바로 다시 연결을 시도하지만 streaming 방식은 연결을 계속 유지하고 HTTP/1.1 의 Chunked 인코딩 방식을 이용해 이벤트가 발생할때마다 서버에서 클라이언트로 결과를 전송한다.

 

streaming은 서버의 상태 변경이 매우 잦은 경우 유리하지만 연결을 길게 맺고 있는 경우 연결의 유효성 관리 등의 부담이 발생한다. 

 

 

해당 방식들은 설명만 훑어봐도 네트워크로 통신하는데 엄청한 cost가 발생할것이라고 예측할 수 있다.  (유감스럽게도 소켓도 마찬가지다)

로드밸런서나 pub-sub pattern을 사용한 서버의 수평 스케일 확장으로 과부하되는 트래픽을 분산하려고 시도할 수 있다고 까지는 들었다..

 

 

같이 읽어보면 좋을 글들)

단방향, 반이중, 전이중 통신

  https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=cashy72&logNo=80013187033 

 

[펌] 단방향 통신, 반이중 통신, 전이중 통신

▪단방향(Simplex) 통신 : 한쪽 방향으로만 전송이 가능한 방식 (예) 라디오, TV ▪반이중(H...

blog.naver.com

PWA (웹앱)

https://perfectmoment.tistory.com/1632

 

PWA(Progressive Web Apps)란 무엇인가? 장점과 구현 사례

모바일 사이트의 개선을 고려할 때, 지금 가장 주목받고 있는 기술 중 하나가 'PWA'입니다. PWA는 '모바일 기기에서 웹사이트를 볼 때 마치 네이티브 앱과 같은 동작을 가능하게 하는 방법'입니다.

perfectmoment.tistory.com

 

Socket.io

https://edu.goorm.io/learn/lecture/557/%ED%95%9C-%EB%88%88%EC%97%90-%EB%81%9D%EB%82%B4%EB%8A%94-node-js/lesson/174379/web-socket%EC%9D%B4%EB%9E%80

 

구름EDU - 모두를 위한 맞춤형 IT교육

구름EDU는 모두를 위한 맞춤형 IT교육 플랫폼입니다. 개인/학교/기업 및 기관 별 최적화된 IT교육 솔루션을 경험해보세요. 기초부터 실무 프로그래밍 교육, 전국 초중고/대학교 온라인 강의, 기업/

edu.goorm.io

 

reference 

 

https://d2.naver.com/helloworld/1052

반응형

'CS > 네트워크' 카테고리의 다른 글

AnyCast란?  (0) 2022.04.11
URL & URI & URN  (0) 2022.02.27
HTTP 메소드 멱등성, 안전한 메소드  (0) 2022.01.31
[TCP] 3-way handshake와 4-way handshake  (0) 2022.01.23
반응형

오류처리(예외처리) 관련 파트이다

 

요즘 기존에 했던 프로젝트 복습?하면서 react의 Suspense 관련 글을 보고 있는데 Suspense가 좋은 이유가

'로직'과 '에러' 처리 부분을 나눌 수 있어서 좋은 코드를 만들 수 있다고 한다. 

Suspense에 관련해서 더욱 흥미로운 내용도 더 많은데 이건 따로 글을 하나 파서 조만간 써봐야겠다. 

 

오류 처리

 

오류 처리는 매우 중요하다.

소프트웨어는 복잡계이고 사람이 작성하다보면 뭔가 빼먹은게 있을 수 있다. 버그는 거의 필연적으로 일어나기 때문에

오류 처리를 우아하게? 해내는것도 중요하다.

오류 코드보다는 예외를 사용하자

if문으로도 오류 처리를 할 수 있지만 그렇게 되면 '로직' 과 오류 처리 코드를 혼동하기 쉽다.

역할을 분리하자

 

try-catch-finally 문을 애용하자

예외처리 부분은 try 부분에 넣고, 꼭 실행되야 하고 오류나 예외처리 부분이 절대로 일어나지 않는다고 가정되는 부분은 finally 부분에 넣는다.

 

미확인 예외를 사용해라 

 

자바스크립트의 경우 거의 반강제(?)로 미확인 예외를 사용하지만...

 

자바의 경우 기본 Exception이 있고 Exception을 상속받아 온갖 종류의 Exception이 있는것으로 알고 있다.

확인 예외(구체화된 Exception 종류)는 명확하지만

에러를 여러단계를 거쳐 throw 받는경우 콜백의 상위 함수에서는 그 오류에 알맞은 Exception handling을 해줘야한다. 이것은 OCP(Open Closed Principle) 를 위반한다.

 

즉, 하위 단계에서 코드를 고치면 상위 단계 메서드 선언부를 전부 고쳐야한다.

일반적으로 애플리케이션은 의존성이라는 비용이 이득보다 크니 미확인 예외를 사용하자.

 

예외에 의미를 제공하자

예외를 던질때는 전후 맥락을 짚을 수 있는 단서를 제공한다. 그럼 오류의 원인과 위치를 찾기 수월해진다.

오류 메세지에 정보를 담아 예외를 던지자.

 

* 다만 production 코드에서 오류 hint를 주는것은 보안 문제가 발생할 수 있으니 주의?

 

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

 

오류를 정의시 프로그래머의 가장 중요한 관심사는 '오류를 잡아내는 방법'

외부 API를 사용 시 감싸기 방식(wrapper)이 최선

 

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 (GMXEError e) {
            throw new PortDeviceFailure(e);
        }
    }
}

LocalPort는 단순한 wrapper class이다.

 

외부 API를 감싸면

1. 외부 라이브러리와 프로그램 사이에서 의존성이 크게 줄어든다.

2. 다른 라이브러리로 갈아타기 쉬워진다.

3. 테스트 할때 API대신 테스트 코드를 넣어주는 방식으로 테스트하기 쉬워진다. 

 

정상 흐름 정의

 

'비즈니스 로직'와 '오류 처리'가 잘 분리된 코드가 좋은 코드

클라이언트 코드가 특수 상황을 처리할 필요가 없게 하자.

 

특수 상황이란 논리와 예외처리를 뒤섞어 막 사용하는 경우를 뜻한다

예를 들어 

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

해당 코드는 직원이 식비를 청구했으면 expense.getTotal() 함수로 식비 청구한 만큼 값을 계산하고

청구하지 않았다면 예외처리를 일으켜서 catch문에서 기본 식비를 계산하는 상황이다.

 

이런경우엔 '비즈니스 로직'으로만 처리해야 좋은 코드이니 주의하자.

 

(* 해당 로직을 올바르게 구현한 객체를 MealExpense 객체로 반환해서 처리하는것을 특수 사례 패턴이라 부른다, 즉 클래스를 만들거나 객체를 조작해 특수 사례를 처리하는 방식) 

 

null을 반환하지 말자

만약 메소드가 제대로 처리 안되었다는 뜻으로 가끔 null을 반환할때도 있는데

그 대신 예외를 던지거나 특수 사례 객체를 반환한다.

 

TS를 쓰면 그나마 양반인데  JS에서 null을 반환하는 순간 스스로 지옥을 만들수도 있을꺼 같다ㅋㅋ

null을 반환하는 대신 빈 리스트를 반환한다면 훨씬 깔끔하다.

외부 API가 null 반환시 감싸기 메서드를 구현해 예외를 던진다.

 

null을 전달하지 말자 

(oauth2 passport 에서 하던거 같던데??)

null을 반환하는거보다 치명적

null을 기대하는 메서드가 아니라면 null을 전달하지말자

 

assert 문으로 null 예외처리를 한다. 

js의 경운 assert 없으니 이런식으로 구현..?

 

function assert(condition, message) {
    if (!condition) {
        throw new Error(message || "Assertion failed");
    }
}

https://stackoverflow.com/questions/15313418/what-is-assert-in-javascript

 

 

결론

 

깨끗한 코드란 가독성뿐만 아니라 안정성도 높아야 한다.

오류 처리와 논리를 분리하면 튼튼하고 깨끗한 코드를 작성 가능, 유지보수성도 크게 높아짐

 

toss 컨퍼런스의 suspense에 대한 내용도 비슷한 내용을 담고 있으니 한번 보는거도 추천한다

 

https://toss.im/slash-21/sessions/3-1

 

프론트엔드 웹 서비스에서 우아하게 비동기 처리하기

API를 호출하거나 네이티브 앱과 통신할 때 프론트엔드 웹 서비스에서는 반드시 비동기 작업이 일어나게 됩니다. 일상처럼 다루고 있지만 정작 UI에서 다루기 힘든 비동기 프로그래밍. React Suspens

toss.im

반응형

'독서 > cleancode' 카테고리의 다른 글

cleancode - unit test  (0) 2022.01.25
clean code - 경계  (0) 2022.01.24
cleancode - 객체와 자료구조  (0) 2022.01.21
clean code - 주석 & 형식 맞추기  (0) 2022.01.20
clean code - 함수  (0) 2022.01.19
반응형

이번 챕터는 객체지향 개념의 챕터인거 같다.

사실 react의 함수형 컴포넌트를 사용하다보면 객체지향과 멀어지는 경향이 있는것 같다.

그래도 사고를 말랑말랑하게 하기위해 읽어보면 좋을듯 싶다.

 

외부 사용자가 클래스 내부 변수에 의존하지 않게 하고, 

외부에서의 조작으로 인해 의도치 않은 변경이나 버그를 막기 위해

흔히 private를 사용한다. (캡슐화, 정보 은닉)

 

이때 의무적으로? get, set 함수를 public 하게 외부에 노출시키는데 기계적으로 노출시키기보다는 

이러한 방식을 좀 더 깊게 고찰해보자는 챕터인것 같다.

 

객체/자료구조

  • 객체는 추상화 뒤로 자료를 숨긴 채 자료를 다루는 함수만 공개(내부 구조 은닉)
  • 자료 구조는 자료를 그대로 공개 (내부 구조 노출)
  •  

객체의 자료 추상화

구현을 감추기 위해선 추상화가 필요하다.

단순히 함수로 변수를 감춘다고 구현이 감춰지는것은 아니다. => 좀 더 고차원적인 사고가 필요

사용자가 어떻게 구현되었는지 몰라도(메소드 내부를 몰라도) 자료를 사용할수 있어야 좋은 코드 작성 방식이다. 

 

자바 코드로 생각해보자. (js는 interface 개념이 없어서 java로 보는게 편하..다?)

//구체적인 Point class이고 명시적 직교 좌표계라는것을 알 수 있다.
public class Point {
	public double x;
	public double y;
}

// 이게 직교 좌표계인지 무엇인지 알수는 없다(추상화 되어있다) 다만 사용하고 싶은
// 자료를 명백히 제공한다.
// 추상적인 Point interface이고 클래스 메서드로만 접근 가능하게 강제한다.
public interface Point {
	double getX();
	double getY();
	void setCartesian(double x, double y);
	double getR();
	double getTheta();
	void setPolar(double r, double theta);
}
//X,Y는 get할때 각각 구하는데 set은 한꺼번에 해야한다. => 개선 필요

위 코드의 public class는 구현을 외부로 노출하고

아래는 구현을 숨긴다.

 

일단 위의 point class는 private로 변수를 선언해도 getter, setter를 외부에 노출한다면 구현을 외부로 노출한다.

 

또 다음 예시를 보자.

//구체적 숫자 값으로 제공
public interface Vehicle {
	public getFuelThankCapacityInGallons();
	public getGallonsOfGasoline();
}

//연료 상태를 백분율이라는 추상적 값으로 제공
public interface Vehicle {
	double getPercentFuelRemaining();
}

위 구체적 인터페이스보다는 자료를 사용할때 추상적으로 제공하는 아래의 인터페이스가 더 좋다.

 

결론적으로, 

구현자는 사용자에게 '내부'를 알지 않아도 사용 가능하도록 추상화하는것이 좋다.

 

절차지향적 vs 객체지향적

본질적으로는 자료(구조)를 어떻게 사용할까에 대한 고민이다.

 

절차지향 

 

절차지향적인 코드는 기존 자료구조를 변경하지 않으면서 새 함수를 추가하기 쉽다.

다만 새로운 자료구조를 추가하기 어렵다. (기존 함수를 전부 바꿔야함)

 

* 새로운 동작을 추가하는 유연성 -> 자료구조, 절차적인 코드

 

객체지향

 

객체지향적인 코드는 기존 함수는 그대로 놔두고 새로운 클래스를 추가하기 쉽다. (상속을 통해?)

다만 새로운 함수를 추가하기 어렵다. (그러기 위해서 모든 클래스를 고쳐야 한다.)

 

* 새로운 자료 타입을 추가하는 유연성 -> 객체 지향

 

서로 상호보완적인 관계이니 무조건 한쪽만 사용하는것은 지양하자.

(객체지향적 사고가 100% 정답은 아님) 

그런데 둘을 혼합하면 잡종 구조가 되니 이런 구조는 서로의 단점만 보이게된다. 그래서 비추천

 

디미터 법칙

잘 알려진 휴리스틱(경험에 기반하여 문제를 해결하거나 학습하거나 발견해 내는 방법, 최고가 아닌 최적의 방식)

모듈은 자신이 조작하는 객체의 속사정을 몰라야 한다는 법칙

 

정확히는 클래스 C의 메서드 f는 아래 객체의 메서드만 호출해야 한다.

  1. 클래스 C
  2. f가 생성한 객체
  3. f 인수로 넘어온 객체
  4. C 인스턴스 변수에 저장된 객체

이때 객체가 반환하는 객체의 메소드를 바로 호출하면 안된다. (잘 모르는것은 경계)

 

기차 충돌

final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();

객체가 한 줄로 이어져서 사용하는 방식으로 디미터 법칙을 무시한다.

일반적으로 조잡하다고 느껴질수 있기 때문에 가능하면 피하는것이 좋다.

 

해당 방식은 다음처럼 고치면 어떻게 될까?

Options opts = ctxt.getOptions();
File scratchDir = opts.getScratchDir();
final String outputDir = scratchDir.getAbsolutePath();

 

이렇게 나눠도 ctext 객체가 opt를 가지고, scratchDir를 가지고 있다는 '내부 구조'를 알 수 있기 때문에

디미터 법칙을 위반할 수 있는 가능성이 존재한다.

그리고 이 위반 가능성은 ctxt가 '자료구조'인지 '객체' 인지에 따라 달라진다.

 

자료구조의 경우 

final String outputDir = ctxt.options.scratchDir.absolutePath;

처럼 사용해도 디미터 법칙의 대상이 되지 않는다.

(자료구조는 내부 구조를 노출해도 되므로 상관X)

 

그럼 자료구조가 아닌, 객체라면 코드를 어떻게 개선해야할까?

'내부 방식'을 몰라도 작동할 수 있도록 개선하면 된다.

 

final String outputDir = ctxt.getScratchAbsolutePath();

 

자료 전달 객체(DTO)

전형적인 자료구조

공개 변수만 있고 함수가 없는 클래스(get, set까진 있다)

 

- 데이터 저장용(데이터베이스와 통신, 소켓에서 받은 메세지 구문 분석시 유용)

- 가공되지 않은 정보를 받아와 애플리케이션 코드에서 사용할 객체로 변환하는 일련의 단계에서 사용 

(raw data)

 

class Address {

	private final String street;
	private final String streetExtra;
	private final String city;
    ...
}

활성 레코드

활성 레코드는 DTO의 특수한 형태

 

공개 변수, 비공계 변수에 조회/설정 함수 제공

save, find 같은 탐색 함수 제공

 

데이터베이스, 다른 소스에서 자료를 직접 변환한 '결과'

비즈니스 로직 메서드를 활성 레코드에 넣는것은 옳지 않다. 자료구조로 취급하자.

 

결론 

 

나는 잡종 구조?를 좀 많이 쓴편이긴 한데 객체와 자료구조를 분리해서 생각해야겠다

또한, 훌륭한 개발자는 편견에 빠지지 않는다. 알맞은 상황에 알맞게 쓰자

* 새로운 동작을 추가하는 유연성 -> 자료구조, 절차적인 코드

* 새로운 자료 타입을 추가하는 유연성 -> 객체 지향

 

 

 

 

반응형

'독서 > cleancode' 카테고리의 다른 글

cleancode - unit test  (0) 2022.01.25
clean code - 경계  (0) 2022.01.24
cleancode - 오류처리  (0) 2022.01.22
clean code - 주석 & 형식 맞추기  (0) 2022.01.20
clean code - 함수  (0) 2022.01.19

반응형

주석

저자는 주석에 매우 부정적인 입장이다.

 

그 이유는

 

1. 주석은 (코드가 계속 변경되며) 최신 내용이 갱신되지 않을수도 있다. 

2. 나쁜 코드를 설명하는 역할을 한다.

3. 주관적 의미를 써놓으면 이해하기가 어렵다

 

주석을 쓰기보다는 명확한 함수, 변수명으로 코드를 표현하기를 원하는것 같다.

 

다만 다음 방식으로는 사용을 추천한다.

 

1. 주의사항

2. 법적인 주석(copyright ~~)

3. 기본적 정보 제공 

ex) 

// kk : mm : ss 형식
const timeMatcher = /\d\d:\d\d:\d\d\.exec('문자열 아무거나...');

4. 의미를 명료하게 밝히는 주석

5. TODO 주석 (다른 프로그래머에게 할일을 알려주는 주석)

 

다만 TODO 주석의 경우 사용할경우 경험상 아무도 맡지 않아서 혼자 미아가.. 되버리는 경우도 있었기 때문에 팀원과 소통을 잘해야한다.

 

결론 : 

 

주석을 달아야한다는 의무감에 빠져서 의미없는 주석을 생산하지 말자.

나도 변수, 함수명으로 내가 하는 목적을 설명하고자 노력하고 있다.

프론트엔드는 특히 주석 === 코드 용량이라 쓰지 말라는 이야기가 있기도..?(minify javascript를 쓰면 되긴한다.)

 

형식 맞추기

'돌아가는 코드' 보다는 코드의 품질을 높이자

팀으로 일한다면 팀이 합의해 규칙을 정하고 모두가 따라야 한다.

가능하면 팀 모두가 도구 사용 -> prettier, esLint?

 

좋은 코드를 만드는법

 

1. 잘게 쪼개자

 

2. 신문 기사처럼 작성(위에서 아래로 볼 수 있게 작성)

 

3. 개념은 빈 행으로 분리 (너무 코드를 빽빽하게 적지 말자), 같은 개념은 가깝게

 

4. 변수는 각 함수 맨 처음에 선언 

 

tmi인데 react에서 useState, hook 등도 항상 최상위에 선언한다.

그 이유는 가독성 장점도 있지만 코드의 흐름이 항상 일관되게 진행될 수 있도록 보장하기 위해서이다.

그리고 react 는 state를 일종의 배열 형태로 관리하고 순서는 보장하기 않기 때문에 항상 선언 순서는 보장되야 한다.

 

5. 인스턴스 변수는 함수 맨 처음에 선언(private 변수 등?)

이유는 2번 참고

 

6. 함수가 다른 함수를 호출하면 가까이 배치, 호출 하는 함수는 호출 되는 함수보다 앞에

이유는 2번 참고 

 

7. 유사한 코드(개념적 친화도가 유사한 코드)는 비슷한 곳에 배치 

 

8. 들여쓰기 기준 통일 

9. 팀 규칙 정하기 (가장 중요)

초기에 시간이 많이 걸려도 규칙은 확실히 정하고 팀원 모두 인지하는게 중요한거 같다.

 

 

 

 

읽은 후기 :

 

사소하지만 중요한 파트라고 생각된다.

특히 팀 규칙 정하는게 정말 중요하다..

팀원 전부의 코드 스타일이 다를 수 있기 때문에(경험상 팀플할때마다 모든 사람이 전부 다 달랐음) 규칙은 반드시 정해야한다.

요즘은 prettier, esLint같은 좋은 도구들이 많이 생겼으니 애용해 보자.

 

반응형

'독서 > cleancode' 카테고리의 다른 글

cleancode - unit test  (0) 2022.01.25
clean code - 경계  (0) 2022.01.24
cleancode - 오류처리  (0) 2022.01.22
cleancode - 객체와 자료구조  (0) 2022.01.21
clean code - 함수  (0) 2022.01.19
반응형

책을 읽고 내용을 메모해두려고 합니다.

1. 함수는 작게

여러 역할을 하는 함수는 역할별로 분리해서 쪼갠다

특히, 함수는 한가지만 잘해야 한다, 한가지만 해야한다.

ex) 이미지가 들어왔을때 이미지 형식이 알맞은지 검사하고, 알맞지 않다면 이미지 정보를 삭제하고 맞다면 저장소에 저장하는 함수

(글로도 벌써 이해하기 힘들다ㅋㅋ)

위 예시 함수는 아래의 3가지 함수로 분리 가능하다.

1. 이미지 형식 검사

2. 이미지 정보 삭제

3. 이미지 저장소에 저장

function yourfuncExample(image){

    if(isVaildImageExt(image)){
        saveImage(image);
    }
    else{
        deleteImage(image);
    }
}

isVaildImageExt(image){
    ... // 형식 확인하는 함수
}

saveImage(image){
    .... // 이미지 저장하는 함수
}

deleteImage(image){
    ... // 이미지 삭제
}

내 경험상 한번 쓴 함수가 다시 뒤에 재사용되는 경우가 많았던걸로 기억한다.

그래서 최대한 잘게 쪼개놓으면 나중 재사용 할 수 있어서 시간절약 및 유지보수에 매우 도움이 된다.

 

2. 서술적인 이름으로

추상화 수준은 동일히

이름을 붙일땐 일관성이 있고 명확해야 한다.

보통 함수는 동작이므로 동사 형태의 이름을 지니며

함수는 같은 문구, 명사, 동사 순서를 지닌다

ex) includeTeardownPages, includeSuiteTearDownPage

 

3. 함수 인수

인수가 많아지면 많아질수록 코드를 이해하는데 어려움이 생긴다. 또한, 테스트할때 매우 복잡해진다.

3항부터는 조금 생각해보자

좀 많아진다고 생각되면 객체로 한번 감싸자

function notGood(a,b,c){
    //이거보다는 
}

function good({a,b,c}){
     // 객체로 한번 감싸서 구조 분해 할당을 활용
}

개발을 하면서 a,b,c의 순서를 외워야할 필요가 없기 때문에 객체(객체 리터럴이나 클래스)로 감싸는거 추천

 

4. side Effect를 일으키지 마라

함수형 프로그래밍의 side Effect 느낌보다는 함수가 자신의 역할 이외에 다른 역할을 추가적으로 한다든지

의도치않은 부수 효과를 일으키는 경우를 말하는것 같다.

예를 들어 dom tree의 element를 여러 함수가 공유해서 같이 조작한다든지 매개변수로 넘어온 객체의 내부 값을 변경한다든지 예상치 못한 결과를 내는 경우를 뜻한다.

사실 1번을 잘 지키면 4번은 저절로 따라온다.

모듈, 함수간 결합도(의존도)를 줄이고 응집도를 높이자.

 

5. 명령과 조회 분리

뭔가 수행하거나, 뭔가 답하거나 여러 종류중 하나만 하는것을 추천

조건이 맞나? isExist ~~

수행? do~~

설정? set~~

 

6. 오류 처리엔 예외처리 이용

function ex(code){

    if(isVaild(code)){ 
        console.log('error');
        return;  //이거보다는 
    }
}

function ex2(code){
    try{
     isVaild(code); // 여기서 throw error
    }
    catch(e){
        console.log(e);
    }

}

예시가 좀 엉성하긴 한데.. 에러처리에는 예외처리를 애용하자. (역할분리)

try catch문 자체도 따로 함수로 뽑아내는것이 좋다.

추가적으로 발생가능한 Error들을 정의하는 Error set을 만들자.

 

7. 반복하지 마라

(중요해서 또나옴..?)

동일한 로직을 함수로 분리하고 재사용하자.

유지보수 및 시간 절약에 큰 도움이 된다.

만약 동일한 로직인 코드를 4개를 만들었다면 뭔가 변경사항이 있을때 4곳을 각각 수정해야한다.

그런데 함수로 분리하고 변경사항을 해당 함수에만 적용한다면 한곳만 고치면 되니 매우 편리해진다.

반응형

'독서 > cleancode' 카테고리의 다른 글

cleancode - unit test  (0) 2022.01.25
clean code - 경계  (0) 2022.01.24
cleancode - 오류처리  (0) 2022.01.22
cleancode - 객체와 자료구조  (0) 2022.01.21
clean code - 주석 & 형식 맞추기  (0) 2022.01.20

+ Recent posts