학교에서 알려주지 않는 17가지 실무 개발 기술 Part1
- 개발 서적
- 2021. 11. 7.
💡 필자가 책을 읽고, 몰랐던 부분이나, 특별히 메모할만한 내용을 추출하여 기록한 포스팅입니다. 책 내용 외에 추가 설명을 덧붙인 부분들이 있습니다.
[학교에서 알려주지 않는 17가지 실무 개발 기술] 구매하러 가기 ⬇
"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."
서문
지은이의 말
(스타트업에서는) 한 분야만 파고드는 개발자가 아닌, 주어진 일정 안에 최소한의 기능을 지닌 제품(MVP, Minimum Viable Product)을 빠르게 만드는 개발자를 필요로 했습니다.
실무에서 공통으로 사용하는 기술과 언젠가 한 번 이상 사용하게 될 기술을 알고 회사에 들어가면 좋을 것 같다는 걸 알게 되었습니다. 이러한 기술들은 소프트웨어 공학 이론과 비교했을 때, 어렵지도 않고 당장 깊게 배워야 할 필요도 없습니다.
1. 문자열 인코딩
1.1. 문자열 인코딩이란
문자열 인코딩과 문자집합(charset)
문자열 인코딩과 문자집합(charset)이라는 용어도 함께 사용합니다. 문자집합(charset)에는 ISO-8895, ASCII 등이 해당합니다. 반면 문자열 인코딩은 문자를 코드로 표현하는 방식을 일컫습니다. 예를 들어 유니코드라는 문자집합(charset)을 표현하는 문자열 인코딩은 UTF-8, UTF-16, UTF-32 등이 있습니다.
1.2. 아스키 코드(ASCII)
48: 숫자 0, 65: 영소문자 a, 67: 영대문자 A
1.3. EUC-KR(CP949)
한국산업 표준으로 지정된 한국어 문자 집합입니다.
문자 하나를 표현하기 위해 2바이트를 사용합니다.
아스키 문자를 표현할 때는 1바이트를 사용하여 아스키코드와 호환됩니다.
0xB0A1 코드가 '가'를 시작으로 '완성형'으로 한글을 표현합니다. (여기서 완성형에 대비되는 '조합형' 개념이 있다. 조합형은 유니코드 2.0버전의 초성, 중성, 종성에 해당하는 코드로 조합하여 한글을 표현할 수 있다.)
버퍼의 크기와 문자열의 길이를 동일하다고 생각해 발생하는 에러가 생각보다 많음에 주의한다.
1.4. 유니코드 (UTF-8, UTF-16, UTF-32)
Universal Coded Character Set + Transformation Format – 8-bit 의 약자입니다. (위키피디아)
UTF-8 이 대중적으로 가장 많이 사용되는 인코딩방식이다. 이는 추후 별도의 포스팅을 작성해보도록 한다.
UTF-16
UTF-16 에서는 기본 다국어 평면에 해당하는(BMP, Basic Multilingual Plane) 해당하는 문자는 2바이트, 그 외에는 4바이트를 활용한다. BMP 가 아닌 평면은 보충 다국어 평면SMP(Supplementary Mltilingual Plane), 상형 문자 보충 평면SIP(Supplementary Ideographic Plane), 특수 목적 보충 평면SP(Supplementary Special-purpose Plane)이 있다.
자바와 윈도우는 유니코드를 사용하기 전부터 고정된 2바이트 길이의 문자 집합을 사용했습니다. 그래서 UTF-16은 멀티바이트라고도 합니다.
BOM(Byte Order Mark)
UTF-16 으로 인코딩된 문자열의 바이트 구성의 맨 앞에는 바이트 순서를 표시하기 위한 BOM 이 포함되어있습니다.
이는 리틀엔디언과 빅엔디언을 구분합니다. (리틀엔디언은 작은 단위의 바이트를 먼저 읽고, 빅엔디언은 큰 단위의 바이트를 먼저 읽는다. UTF-16 에서는 이를 문자열의 맨 앞에 2바이트를 할당하여 리틀엔디언인지 빅엔디언인지를 표현해주는데, FE -> FF 순서이므로 FE 쪽부터 읽으라는 의미로 해석할 수 있다. 0xFF, 0xFE 순서로 나타난다면 리틀엔디언, 0xFE, 0xFF 순서로 나타난다면 빅엔디언을 의미한다.)
0xFF 0xFE 0x48 0xC5
로 나타난 인코딩을 풀어서 해석하면, 리틀엔디언이므로 FE 쪽에서부터 읽어주어야 한다. 그렇게 조합하면 C548
이 되고 이는 유니코드 '안'에 해당됩니다.
요즘 컴퓨터는 대부분 '리틀엔디언'방식으로 작동합니다.
UTF-8 에서도 BOM 에 해당하는 값이 있지만, 1바이트 단위로 글자를 변환하기 때문에 글자를 읽는 순서가 달라도 영향을 받지 않습니다. 반면, UTF-16, UTF-32 는 문자 하나가 차지하는 공간이 최소 2바이트 이기 때문에 어떤 바이트(앞 또는 뒤)를 먼저 읽을지 명시되어있어야 합니다. (대부분 프로그램이나 라이브러리는 BOM 을 만나도 무시하고 넘어간다. 심지어 JSON 은 BOM 을 허용하지 않는다.)
MySQL 의 두 가지 UTF-8 타입: utf8, utf8mb4
utf8 은 3바이트까지 정상적으로 처리하지만, 4바이트 영역 문자는 처리하지 못합니다. 따라서 UTF-8 과 완벽히 호환되는 문자 집합을 쓰고 싶다면 utf8mb4 를 사용하여야 합니다.
2. 다국어 처리 i18n
책에서는 파이썬의 gettext 라이브러리를 활용하여 다국어 지원의 개략적인 방법을 소개합니다.
다국어 지원시 주의할 점은, 언어 설정 기준을 어떻게 할지 확실하게 정하는 것입니다. 예를 들어 시스템 언어 설정을 따를지, 웹 브라우저의 요청 언어를 따를지, 요청 IP 의 국가를 기준으로 결정할지 등을 선택해야 합니다.
3. 날짜와 시간
3.2. 단조 시간
단조시간(monotonic time) 은 운영체제 또는 CPU 와 같은 하드웨어에서 직접 계산하는 시간입니다. 실제 세계 시간과는 다르며, 운영체제 시작 이후 바뀌지 않는 특성이 있습니다. 사용자가 임의로 값을 변경할 수 없으며, 운영체제 재부팅시 초기화됩니다.
운영체제 시작 이후 바뀌지 않는 단조시간의 특성을 활용하여 특저 두 시점을 비교하는 데 활용할 수 있습니다. 가령 두 작업 사이에 걸린 시간을 측정하거나, 일정한 시간 간격마다 작업이 수행될 수 있도록 합니다.
3.3. 실제 시간
실제 시간(real time)은 벽 시계(wall clock)으로 부르기도 합니다. 실제 시간은 시간 서버로부터 주기적으로 시간 값을 가져와 동기화하기 때문에, 단조시간처럼 두 시간 차이를 측정하는데 활용하기에는 무리가 있습니다.
하지만, 운영체제의 부팅 주기보다 긴 시간을 지정한다거나(1달주기) 특정 날짜에 반드시 실행해야하는 작업의 기준으로 사용하기에는 적절합니다.
파이썬의 time 라이브러리를 활용하여 단조시간은 time.monotonic()
으로 호출할 수 있습니다.
파이썬의 datetime 라이브러리를 활용하여 실제시간은 datetime.datetime.now()
으로 호출할 수 있습니다.
3.4. 타임 존
UTC 는 Coordinated Universal Time 을 GMT 는 Greenwich Mean Time 는 개발자에게는 동일한 시간대이지만, 엄밀히 세계협정시(UTC)가 기준입니다. GMT 는 여러 타임 존 중 하나일 뿐으로 UTC+0 값을 갖기 때문에 UTC 와 시간 자체는 동일합니다.
실무에서는 서비스 발견 기능을 사용해 여러 서버가 같은 타임 존을 사용하는지 확인할 수 있습니다. ETCD 나 주키퍼(zookeeper)를 사용합니다.
4. 정규표현식
4.1. 정규표현식 기초
문자 1개 찾기
문자를 직접 입력하는 것은 해당 문자를 그대로 가리킨다는 것을 의미합니다.
파이썬에서 find_pattern('o', 'Hello World')
를 실행하면 다음과 같은 결과를 출력합니다. ['o','o']
대괄호([])와 하이픈(-)을 활용하여 문자 범위를 지정할 수 있습니다.
[a-z]
는 영소문자 1개를 의미하며[A-Z]
는 영대문자 1개를 의미합니다.[a-zA-Z]
는 영대소문자 1개를 의미합니다.[0-9]
는 숫자 1개를 의미합니다.
특수문자 중 일부는 정규 표현식의 특정 기능을 나타내는 문자입니다. 가령 대괄호는 문자 1개를 지정하는 구분기호였습니다. 따라서 실제로 문자열 중에서 '[' 를 찾고 싶다면 '\\[' 처럼 써주어야 합니다. 프로그래밍 언어는 '\\' 의 첫 번째 \가 escape 지정자로 인식하여 '\\'를 '\'로 인식하고 이는 결국 '[' 앞에서 비로소 이스케이프 문자 '\'로 작동합니다. 마찬가지로 '\' 글자 하나를 인식하기 위해서는 '\\\\'와 같이 지정해주어야 합니다. (그러나, 이는 regexr.com, regexr101.com 같은 사이트에서는 \ 하나만으로 이스케이프 할 수 있는것처럼 보인다.)
\w
는 특수문자, 공백을 제외한 글자 1개를 의미합니다.\W
는 특수문자와 공백만을 의미합니다. (\w 와 반대)[^A-Z]
대괄호 안 캐럿은 NOT 을 의미합니다. 따라서 이는 대문자를 제외한 모든 글자 1개를 의미합니다.^[A-Z]
대괄호 밖 캐럿은 줄(line)의 시작을 의미합니다. 따라서 이는 줄의 시작이 대문자라면 지시합니다..
대괄호 밖 . 은 모든 글자를 포함하는 패턴입니다. 점문자 그 자체를 지정하고자 한다면 [.]과같이 지시합니다.
하나 이상의 문자 찾기
[a-z]+
패턴을 이용하면 소문자 문자열을 지시합니다. 파이썬에서 find_pattern('[a-z]+', r'Hello World') 라고 하면 다음과 같은 결과를 출력합니다. ['ello', 'orld'][a-z0-9,]{3}
패턴은 {3} 앞 문자가 3번만 일치(고정길이)하는 경우만을 지시합니다. 파이썬에서 find_pattern('[a-z0-9,]{3}', r'Hello World, 1,2,3,4,5,') 라고 하면 다음과 같은 결과를 출력합니다. ['ell', 'orl', '1,2', ',3,', '4,5'][a-z0-9,]{3,5}
패턴에서 {3,5}는 반복횟수의 범위를 의미합니다. 앞선 패턴이 3번~5번인 출현하는 경우를 지시합니다. {3,} 처럼 최소 반복횟수만을 지정할 수도 있고, {,5} 처럼 최대 반복횟수만을 지정할 수도 있습니다., {,5}
그 외 유용한 패턴들
위에서 살펴본 내용을 요약하면 다음과 같습니다.
- [] 를 활용해서 문자 1개를 지시합니다.
- {} 를 활용해서 해당 문자가 몇번 반복되는지 지시합니다.
- ^, $ 를 활용해서 줄(line)의 시작과 끝을 지시합니다.
위 세가지를 조합하여
^[a-zA-Z]{5}
영문자 5글자가 줄의 시작에서 나타나는 경우[a-zA-Z]{3}$
영문자 3글자가 줄의 끝에서 나타나는 경우
와 같이 지정할 수 있습니다.
4.2. 실사용 예
비밀번호의 길이 및 특수문자가 포함되도록 지정하거나, 이메일을 검증하거나, 원하는 특별한 문자열 패턴을 지정하여 크롤링에 활용할 수 있습니다.
RFC5332
이메일 정규 표현식 표준은 RFC5332 에 지정되어있습니다.
4.3. 마치며
정규표현식은 Perl, POSIX, POSIX 확장 등 다양한 형태로 존재하기에 조금씩 그 사용 형태가 다를 수 있습니다.
정규표현식은 느릴 수 있습니다. 그런 경우, 정규식 외 다른 접근 방법을 고려해볼 필요가 있습니다.
정규표현식을 시각적으로 확인할 수 있는 regexr.com, regex101.com 등을 활용합니다.
5. 범용 고유 식별자 UUID
UUID: Universally Unique Identifier
범용고유식별자란 컴퓨터 시스템 내에서 고유한 객체를 식별하기 위해 사용하는 값입니다. 네트워크상에 존재하는 여러 컴퓨터를 식별하기 위해 사용하기 시작했습니다. 현재는 작업식별자로 활용되는 경우가 흔합니다.
5.1. UUID 의 구조
UUID 는 하이픈 4개와 16진수 32개로 구성되어있습니다.
8개-4개-4개-4개-12개 의 구조를 반드시 유지해야합니다.
하이픈은 고정된 위치의 구분자이기 때문에 용량에 영향을 주지 않습니다.
UUID 는 총 32개의 16진수(4비트=0.5바이트)로 이루어져, 16바이트가 필요합니다.
5.2. UUID 버전
가장 널리 사용되는 버전은 4 입니다. 총 126비트 중 4비트는 버전을, 나머지 122비트는 무작위로 생성된 값입니다.
5.3. 실사용 예
UUID 의 일부에 원하는 정보를 포함시킬 수 있습니다. 가령 앞 8자리를 타임스탬프로 활용할 수 있습니다.
6. 난수
6.1. 유사 난수
컴퓨터는 이론적으로 완벽한 난수를 생성할 수 없습니다. 컴퓨터는 난수표, 난수 알고리즘, 알고리즘 초기화에 사용할 시드 값으로 난수를 만듭니다. 이런 식으로 만들어진 난수를 유사난수라고 합니다.
유사 난수를 활용하여 난수 생성할 때마다 같은 시드를 사용하면 동일한 난수가 생성됩니다. 따라서, 오늘날에는 열 잡음, 광전자 등 신호의 노이즈를 이용해 시드가 필요 없는 HRNG(Hardware Random Number Generator)를 사용하기도 합니다.
메르센 트위스터 유사난수
대부분의 언어와 프레임워크에서는 '메르센 트위스터 - Mersenne Twister' 방법을 사용하여 유사난수를 생성합니다. 메르센 트위스터는 충분히 큰 난수 주기를 가지고 있고, 난수 분포가 균일하고, 생성 속도가 빠릅니다. 항상 바뀌는 시드를 사용한다면, 고른 분포의 난수를 만들어낼 수 있습니다.
시드값의 중요성
시드값이 동일하면 같은 난수가 생성된다는 점을 악용한(또는 지혜롭게 활용한) 다음과 같은 사례가 있습니다.
- 닌텐도 플랫폼으로 발매된 포켓몬스터 게임은 '게임을 시작하고 최초로 버튼을 입력하는 시점'을 시드 값으로 사용했다. 이를 통해 난수를 예측하여 특정 포켓몬을 얻을 수 있었다. 이 기술은 사용자들 사이에 loop, RNG abuse(랜덤 어뷰즈)라고 불리며 아직도 사용된다.
- 모바일 플랫폼으로 발매된 '에픽세븐' 게임은 정밀도가 낮은 시간 값을 시드 값으로 사용했고, 특정 시간에 동일한 아이템을 얻을 수 있는 문제가 있었다.
6.2. 암호학적으로 안전한 난수
유사난수에 비해 생성속도가 비교적 느리지만, 시드값을 사용하지 않기 때문에 예측이 불가능하다는 장점이 있습니다.
리눅스/유닉스는 /dev/urandom 파일을 읽은 값을 활용(엔트로피 풀이라는 개념을 만들어두는 것을 의미하는데, 키보드, 마우스 입력, 인터럽트 등의 노이즈 신호를 이용)하고, 윈도우는 BCryptGenRandom() 함수에서 반환한 값을 사용하여 노이즈에 기반한 난수를 얻습니다. 파이썬에서 os 라이브러리의 urandom 함수(인자는 결과값으로 얻을 바이트수를 넣어줌)를 활용하여 랜덤값을 얻을 수 있습니다.
난수 생성 시간은 유사난수에 비해 느립니다.
6.3. 공정한 난수, 셔플 백
제비뽑기는 반드시 누군가는 당첨되게 됩니다. '섞여진 가방'이라는 의미의 셔플 백은 공정한 난수를 의미합니다.
7. 해시함수
7.1. 해시함수 정의
해시함수는 임의의 입력값을 고정된 길이의 해시 값으로 변환하는 함수입니다.
암호학적으로 안전한 해시함수는 보안 기능이 활용될 수 있습니다.
암호학적으로 안전하지 않은 함수도 비교적 빠르다는 점을 활용하여 파일의 무결성 검사, 해시맵의 키로 활용합니다.
역상 공격
암호학적으로 안전한 해시함수는 다음 두 공격에 대비되어있어야 합니다.
- 제1 역상 공격: 해시 값으로 입력 값을 복원하는 방법
- 제2 역상 공격: 서로 다른 입력값으로 같거나 비슷한 해시 값을 찾는 방법
7.3. 해시함수의 종류
MD5, SHA-1, SHA-2(SHA-256, SHA-384, SHA-512)
안전하지 않은 해시함수
MD5 는 최근 컴퓨팅 성능으로 쉽게 해시충돌을 만들어낼 수 있습니다. 그러므로 안전하지 않습니다. 마찬가지로 SHA-1 또한 2017년 CWI(네덜란드 국립 정보 연구소)에서 서로 다른 입력값으로 해시 충돌 방법을 발견됨으로써, 더 이상 안전하지 않습니다.
이 외에 현재 안전하다고 판단하여 사용되고 있는 해시함수들도 언젠가는 더 빠르고 효율적인 공격방법이 나올 수 있습니다.
7.5. 실사용 예
비밀번호
SHA-256, SHA-512 해시 함수를 사용하면 비밀번호와 같은 민감한 데이터를 안전히 저장할 수 있습니다. 하지만, 이 두 함수는 해시 값을 계산하는 비용이 매우 크므로 무차별 대입 공격(Brute Force Attack)이나 서비스 거부 공격(Denial of Service Attack, 일명 디도스 DDOS)과 같이 매우 많은 요청을 동시에 보낼 경우 쉽게 서비스 장애가 생길 수 있습니다.
대규모 사용자를 대상으로 한 서비스를 개발할 때는 멀티코어를 활용할 수 있는 해시함수인 Blake2b 해시 함수를 사용해야 합니다.
바이너리 데이터의 무결성 검증
대용량 파일이 서로 같은 파일임이 검증하는 데 해시 함수를 활용할 수 있습니다.
해시 충돌과 생일 역설
두 사람이 생일이 같을 확률은 매우 낮습니다. 하지만, 23명이 모였을 때 생일이 같은 두 사람이 있을 확률은 50%가 넘으며, 57명이 모이면 99%가 넘습니다.
마찬가지로, 해시 값도 생성된 키가 많으면 많을 수록 충돌확률이 늘어나게 되므로, 충돌가능성을 염두에 두는 프로그래밍을 해야 합니다.
사람이 365명이 넘는 경우에는 100% 확률로 생일이 같은 사람이 존재합니다.
n < 365 인 경우는, 모든 사람의 생일이 모두 다른 사건의 여사건으로 그 확률을 구할 수 있습니다.
'개발 서적' 카테고리의 다른 글
학교에서 알려주지 않는 17가지 실무 개발 기술 15장 (0) | 2022.08.25 |
---|---|
학교에서 알려주지 않는 17가지 실무 개발 기술 14장 (0) | 2022.05.07 |
학교에서 알려주지 않는 17가지 실무 개발 기술 Part2 요약 (0) | 2022.03.26 |
앱 highlight 떠나보내주기: 책 속에 밑줄 긋기 (0) | 2021.11.04 |
책장 파먹기 (0) | 2021.11.01 |