반응형

 

test 종류에 관한 이야기는 간략히 

 

https://lodado.tistory.com/category/%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4%EA%B3%B5%ED%95%99

 

'소프트웨어공학' 카테고리의 글 목록

https://github.com/lodado 이전 블로그 https://blog.naver.com/ycp998

lodado.tistory.com

에 적었다.

 

테스트가 좋다는건 많은 사람들이 알지만 제대로 된 테스트 케이스를 작성하는건 어려운것 같다.

 

TDD(Test Driven Development)란?

 

테스트가 개발을 이끌어 나간다.

기본 개발 방식은 요구사항 분석 -> 구현 -> 테스트라면 

TDD는 요구사항 분석 -> 테스트 -> 구현

 

그렇다면 TDD는 어떤 상황에서 해야할까?

처음해보는 프로그램 주제
나에 대한 불확실성이 높은 경우
고객의 요구조건이 바뀔 수 있는 프로젝트
외부적인 불확실성이 높은 경우
개발하는 중에 코드를 많이 바꿔야 된다고 생각하는 경우
내가 개발하고 나서 이 코드를 누가 유지보수할지 모르는 경우
즉, 불확실성이 높을 때 TDD를 하면 된다.

불확실성이 높다 -> 피드백 & 협력이 중요한 경우 

 

TDD 법칙

 

1. 실패하는 단위 테스트를 작성할 때까지 실제 코드를 작성하지 않는다.

2. 컴파일은 실패하지 않으면서 실행이 실패하는 정도로만 단위 테스트를 작성한다.

3. 현재 실패하는 테스트를 통과할 정도로만 실제 코드를 작성한다.

중요한것은 지저분한 테스트코드는 안하느니만 못하다는 것이다.

중요도로 따지면 기능 === 테스트 코드이다.

 

좋은 테스트 코드는 유연성, 유지보수성, 재사용성을 제공한다. 

즉, 테스트 코드가 좋으면 변경이 쉬워진다.

리펙토링에 거부감이 없어진다.

 

 

깨끗한 테스트 코드란?

 

가독성이 좋은 코드

명료성, 단순성, 풍부한 표현력 (중복성 제거)

 

최소의 표현으로 많은것을 나타냄

 

BUILD-OPERATE-CHECK 패턴을 추천

 

1. BUILD - 테스트 자료

2. OPERATE - 테스트 조작

3. CHECK - 조작 결과 체크(boolean식으로 체크하는게 좋음)

 

도메인(DSL)에 특화된 테스트 언어

API 위에다 함수, 유틸리티 구현 후 사용(경계-분리 엄청 좋아하시는거 같다)

테스트 당사자와 독자를 도와주는 테스트 언어

 

이중 표준

제한된 자원, 환경적 문제로 인해서

테스트 코드가 기능 코드랑 똑같은 환경, 컨벤션일 필요는 없다.

(mocking 관련?)

 

테스트당 개념 하나

 

테스트당 assert 하나라는 개념도 있다.

그것도 나쁘지는 않지만 저자는 기능 '하나'당 테스트 '하나' 추천

 

F.I.R.S.T 개념

 

Fast

테스트는 빠르게

(tmi로 cypress보다는 jest가 당연히 빠르고, 느리면 테스트를 주저하게됨)

 

Independent

각 테스트는 서로 의존적이지 않다.

독립된 환경설정

이유 : 독립되야 어디서 진짜 문제가 생겼는지 알기 쉽다 

 

Repeatable

어느 환경에서도 반복 가능, 동일한 결과

 

Self-Validating

bool 값으로 테스트 결과를 내야한다.

테스트를 파헤치지 않아도 결과를 알 수 있어야 한다.


Timely

테스트를 적시에 작성

단위 테스트는 테스트하려는 실제 코드를 구현하기 직전에 구현

 

결론

 

사실 '테스트' 라는 주제 하나만으로도 책 한권을 쓸 정도로 어려운 부분이다.

테스트 코드는 실제 코드만큼 중요하다

 

개인적 경험으로는 프로젝트때 테스트 코드를 한번 도입해보려고 시도했는데 

무작정 도입하고 보니 '깨끗하지 않은 테스트 코드'의 표본이 된거 같아서

이 글에서 말한 없느니만 못한 테스트가 된거 같기도 하다 😂

 

그래서 요즘 괜찮은 테스트를 짜기 위해 이론적으로 공부중이다.

 

테스트는 개인적으로 팀원간의 '공유 작업 파트'를 변경시 문제점을 빨리 캐치하고

커뮤니케이션 비용을 줄이는 측면에서 큰 도움이 된다고 생각되고,

또한 리펙토링의 거부감을 줄이는데 이점이 있다고 생각된다.

 

그밖의 tmi)

 

tistory에서 글자 색깔을 바꿀 수 있는걸 배웠다!

 

reference 

 

https://gmlwjd9405.github.io/2018/06/03/agile-tdd.html

 

[Agile] TDD(테스트 주도 개발)란 - Heee's Development Blog

Step by step goes a long way.

gmlwjd9405.github.io

 

반응형

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

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

 

 

 

테스트의 종류

Unit Test

테스트할 수 있는 가장 작은 단위(함수)로 기능을 쪼개서 테스트하는 방식

만약 문제가 일어난다면 어디서 일어나는지 가장 효과적으로 알 수 있다.

가장 좋은 방식이라 생각되는 테스트

 

도구) jest, mocha 등 사용

 

보통 jest의 경우는 프론트는 프론트만, 백엔드는 백엔드의 기능만 테스트하고 다른 부분은 mocking해서 

정상적으로 동작한다고 가정하고 해당 'Unit'만 테스트하는 방식이다.

 

-> 기능 테스트 위주로 하는거 추천?

 

Intergration Test

 

말 그대로 코드들을 '통합' 해서 문제가 없는지 확인하는 테스트

보통은 협업할때 내 코드와 팀원의 코드가 결합되도 문제가 없는지 확인하는 테스트이다.

커뮤니케이션적인 측면이 많이 필요한 테스트라 생각이 든다

 

도구) jest, cypress 등 사용

 

E2E(End-To-End) test

유저의 입장에서 내가 만든 프로그램이 의도대로 제대로 작동하는지 확인하는 테스트 

front, backend 전부 하나로 묶어서 테스트하고 실제 배포전에 문제를 발견하기 가장 쉬운 방법이지만

'어디서' 문제가 발견되는지는 까다로울 수 있다. (front인지 backend인지, 기능 여러개를 묶었다면 어디서 오류가 발생된건지 E2E test만으로는 발견하기 어려울 수 있음)

 

따라서 E2E test 만 하는것보다는 Unit test와 병행해서 하는것을 추천한다.

 

도구) 셀레니움, cypress 등 사용

 

-> UI 테스트는 E2E로 하는거 추천?

 

좋은 테스트의 비율?

 

구글 컨퍼런스에 따르면, 

Google often suggests a 70/20/10 split: 70% unit tests, 20% integration tests, and 10% end-to-end tests. 

라고 한다.

 

그외 팁으로는 

  • 기존의 레거시 코드는 E2E 테스트로 커버하라.
  • 새로 개발하거나 변경하는 부분을 대상으로 우선적으로 단위 테스트를 시작하라.

E2E 테스트 또한 기능이 복잡하거나 중요한 기능부터 커버하기 시작하면 효율적으로 단위테스트 적용이 가능할 것이다.

 

TEST의 장점

- 리펙토링시 test가 안정성을 담보하므로 거침없이 리펙토링을 할 수 있다.

- 코드 변경으로 어떤 부분이 영향이 있는지 빨리 파악 가능하다

- 테스트 자동화시(CI) 실수를 빨리 캐치하고 커뮤니케이션 비용을 줄일 수 있다.

- 버그 수정에 드는 시간을 줄일 수 있다

- test를 짜는데 초반엔 시간이 많이 들어도 장기적으로 보면 시간을 절약할 수 있다.

TEST의 단점

살충제 패러독스 

 

동일한 테스트 케이스를 사용하여 반복적으로 테스트를 수행하면 새로운 버그를 찾지 못한다.

소프트웨어는 복잡계이고, 테스트를 다 통과했다고 완벽한 소프트웨어는 아니다.

내가 예상치 못한 부분에 버그가 숨겨져 있을 수 있다.

 

- 러닝 커브

- 팀이 테스트에 대해 미적지근한 반응이라면 이도저도 안될수도 있음

 

같이 보면 좋을 동영상)

 

https://www.youtube.com/watch?v=jdlBu2vFv58 

 

reference 

 

https://codeahoy.com/2016/07/05/unit-integration-and-end-to-end-tests-finding-the-right-balance/

 

Unit, Integration and End-To-End Tests - Finding the Right Balance

Explore the difference between unit, integration and end-to-end tests and how to choose between them.

codeahoy.com

 

https://www.popit.kr/unit-test-%EB%8B%A8%EC%9C%84-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EB%8F%84%EC%9E%85%ED%95%98%EA%B8%B0-1%ED%8E%B8/

 

Unit Test (단위 테스트) 도입하기 - 1편 | Popit

원문 블로그 주소 : https://gregor77.github.io/2019/08/16/about-unit-test/ 관련글 : https://www.popit.kr/unit-test-단위-테스트-도입하기-2편/ 지금 부서의 역할이 사내에서 제품을 가지고 있는 팀들과 협업을 하면서

www.popit.kr

 

반응형
반응형

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

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

 

외부 코드 사용하기


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

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

 

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

+ Recent posts