forked from 738/clean-code-typescript
-
Notifications
You must be signed in to change notification settings - Fork 2
/
README.md
2917 lines (2135 loc) · 82.7 KB
/
README.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<p align="center">
<img width="380" src="https://github.com/738/clean-code-typescript/blob/master/clean-code-typescript.png?raw=true" />
</p>
<h1 align="center">
clean-code-typescript
</h1>
<p align="center">
<a href="https://twitter.com/intent/tweet?text=클린코드%20타입스크립트&url=https://github.com/738/clean-code-typescript">
<img src="https://img.shields.io/twitter/url/http/shields.io.svg?style=social" alt="Tweet">
</a>
<a href="https://hits.seeyoufarm.com">
<img src="https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2F738%2Fclean-code-typescript" alt="Tweet">
</a>
</p>
<p align="center">
<b>타입스크립트를 위한 클린코드</b>
</p>
<p align="center">
<a href="https://github.com/ryanmcdermott/clean-code-javascript">clean-code-javascript</a>에서 영감을 받았습니다.
</p>
## 목차
1. [소개](#소개)
2. [변수](#변수)
3. [함수](#함수)
4. [객체와 자료구조](#객체와-자료구조)
5. [클래스](#클래스)
6. [SOLID](#solid)
7. [테스트](#테스트)
8. [동시성](#동시성)
9. [에러 처리](#에러-처리)
10. [서식](#서식)
11. [주석](#주석)
12. [번역](#번역)
13. [번역에 도움을 주신 분들](#번역에-도움을-주신-분들)
## 소개
![Humorous image of software quality estimation as a count of how many expletives
you shout when reading code](https://www.osnews.com/images/comics/wtfm.jpg)
Robert C. Martin의 책인 [*클린 코드*](http://www.yes24.com/Product/Goods/11681152)에 있는 소프트웨어 공학 방법론을 타입스크립트에 적용한 글입니다. 이 글은 스타일 가이드가 아닙니다. 이 글은 타입스크립트에서 [읽기 쉽고, 재사용 가능하며, 리팩토링 가능한](https://github.com/ryanmcdermott/3rs-of-software-architecture) 소프트웨어를 작성하기 위한 가이드입니다.
여기 있는 모든 규칙을 엄격하게 따를 필요는 없으며, 보편적으로 통용되는 규칙은 아닙니다. 이 글은 하나의 지침일 뿐이며, *클린 코드*의 저자가 수년간 경험한 내용을 바탕으로 정리한 것입니다.
소프트웨어 공학 기술의 역사는 50년이 조금 넘었고, 배워야 할 것이 여전히 많습니다. 소프트웨어 설계가 건축 설계만큼 오래되었을 때는 아마도 아래 규칙들보다 엄격한 규칙을 따라야 할 것입니다. 하지만 지금은 이 지침을 당신과 당신 팀이 작성하는 타입스크립트 코드의 품질을 평가하는 기준으로 삼으세요.
한 가지 더 말씀드리자면, 이 규칙들을 알게 된다 해서 당장 더 나은 개발자가 되는 것은 아니며 코드를 작성할 때 실수를 하지 않게 해주는 것은 아닙니다. 젖은 점토가 최종의 결과물로 빚어지는 것처럼 모든 코드들도 처음 작성한 코드로 시작됩니다. 결국은 동료들과 리뷰하면서 결점이 제거됩니다. 당신이 처음 작성한 코드에 개선이 필요할 때 자책하지 마세요. 대신 코드가 더 나아지도록 두들기세요!
**[⬆ 맨 위로 이동](#목차)**
## 변수
### 의미있는 변수 이름을 사용하세요
읽는 사람으로 하여금 변수마다 어떤 점이 다른지 알 수 있도록 이름을 구별하세요.
**Bad:**
```ts
function between<T>(a1: T, a2: T, a3: T): boolean {
return a2 <= a1 && a1 <= a3;
}
```
**Good:**
```ts
function between<T>(value: T, left: T, right: T): boolean {
return left <= value && value <= right;
}
```
**[⬆ 맨 위로 이동](#목차)**
### 발음할 수 있는 변수 이름을 사용하세요
발음할 수 없는 이름은 그 변수에 대해서 바보 같이 소리를 내 토론할 수밖에 없습니다.
**Bad:**
```ts
type DtaRcrd102 = {
genymdhms: Date;
modymdhms: Date;
pszqint: number;
}
```
**Good:**
```ts
type Customer = {
generationTimestamp: Date;
modificationTimestamp: Date;
recordId: number;
}
```
**[⬆ 맨 위로 이동](#목차)**
### 동일한 유형의 변수는 동일한 단어를 사용하세요
**Bad:**
```ts
function getUserInfo(): User;
function getUserDetails(): User;
function getUserData(): User;
```
**Good:**
```ts
function getUser(): User;
```
**[⬆ 맨 위로 이동](#목차)**
### 검색할 수 있는 이름을 사용하세요
코드를 쓸 때보다 읽을 때가 더 많기 때문에 우리가 쓰는 코드는 읽을 수 있고 검색이 가능해야 합니다. 프로그램을 이해할 때 의미있는 변수 이름을 짓지 않으면 읽는 사람으로 하여금 어려움을 줄 수 있습니다. 검색 가능한 이름을 지으세요. [TSLint](https://palantir.github.io/tslint/rules/no-magic-numbers/)와 같은 도구는 이름이 없는 상수를 식별할 수 있도록 도와줍니다.
**Bad:**
```ts
// 86400000이 도대체 뭐지?
setTimeout(restart, 86400000);
```
**Good:**
```ts
// 대문자로 이루어진 상수로 선언하세요.
const MILLISECONDS_IN_A_DAY = 24 * 60 * 60 * 1000;
setTimeout(restart, MILLISECONDS_IN_A_DAY);
```
**[⬆ 맨 위로 이동](#목차)**
### 의도를 나타내는 변수를 사용하세요
**Bad:**
```ts
declare const users: Map<string, User>;
for (const keyValue of users) {
// users 맵을 순회
}
```
**Good:**
```ts
declare const users: Map<string, User>;
for (const [id, user] of users) {
// users 맵을 순회
}
```
**[⬆ 맨 위로 이동](#목차)**
### 암시하는 이름은 사용하지 마세요
명시적인 것이 암시적인 것보다 좋습니다.
*명료함은 최고입니다.*
**Bad:**
```ts
const u = getUser();
const s = getSubscription();
const t = charge(u, s);
```
**Good:**
```ts
const user = getUser();
const subscription = getSubscription();
const transaction = charge(user, subscription);
```
**[⬆ 맨 위로 이동](#목차)**
### 불필요한 문맥은 추가하지 마세요
클래스/타입/객체의 이름에 의미가 담겨있다면, 변수 이름에서 반복하지 마세요.
**Bad:**
```ts
type Car = {
carMake: string;
carModel: string;
carColor: string;
}
function print(car: Car): void {
console.log(`${car.carMake} ${car.carModel} (${car.carColor})`);
}
```
**Good:**
```ts
type Car = {
make: string;
model: string;
color: string;
}
function print(car: Car): void {
console.log(`${car.make} ${car.model} (${car.color})`);
}
```
**[⬆ 맨 위로 이동](#목차)**
### short circuiting이나 조건문 대신 기본 매개변수를 사용하세요
기본 매개변수는 short circuiting보다 보통 명료합니다.
**Bad:**
```ts
function loadPages(count?: number) {
const loadCount = count !== undefined ? count : 10;
// ...
}
```
**Good:**
```ts
function loadPages(count: number = 10) {
// ...
}
```
**[⬆ 맨 위로 이동](#목차)**
### 의도를 알려주기 위해 `enum`을 사용하세요
예를 들어 그것들의 값 자체보다 값이 구별되어야 할 때와 같이 코드의 의도를 알려주는데에 `enum`은 도움을 줄 수 있습니다.
**Bad:**
```ts
const GENRE = {
ROMANTIC: 'romantic',
DRAMA: 'drama',
COMEDY: 'comedy',
DOCUMENTARY: 'documentary',
}
projector.configureFilm(GENRE.COMEDY);
class Projector {
// Projector의 선언
configureFilm(genre) {
switch (genre) {
case GENRE.ROMANTIC:
// 실행되어야 하는 로직
}
}
}
```
**Good:**
```ts
enum GENRE {
ROMANTIC,
DRAMA,
COMEDY,
DOCUMENTARY,
}
projector.configureFilm(GENRE.COMEDY);
class Projector {
// Projector의 선언
configureFilm(genre) {
switch (genre) {
case GENRE.ROMANTIC:
// 실행되어야 하는 로직
}
}
}
```
**[⬆ 맨 위로 이동](#목차)**
## 함수
### 함수의 매개변수는 2개 혹은 그 이하가 이상적입니다
함수 매개변수의 개수를 제한하는 것은 함수를 테스트하기 쉽게 만들어주기 때문에 놀라울 정도로 중요합니다.
함수 매개변수가 3개 이상인 경우, 각기 다른 인수로 여러 다른 케이스를 테스트해야 하므로 경우의 수가 매우 많아집니다.
한 개 혹은 두 개의 매개변수가 이상적인 경우고, 가능하다면 세 개는 피해야 합니다. 그 이상의 경우에는 합쳐야 합니다.
두 개 이상의 매개변수를 가질 경우, 함수가 많은 것을 할 가능성이 높아집니다.
그렇지 않은 경우, 대부분 상위 객체는 하나의 매개변수로 충분할 것입니다.
많은 매개변수를 사용해야 한다면 객체 리터럴을 사용하는 것을 고려해보세요.
함수가 기대하는 속성을 명확하게 하기 위해, [구조 분해](https://basarat.gitbook.io/typescript/future-javascript/destructuring) 구문을 사용할 수 있습니다.
이 구문은 몇 개의 장점을 가지고 있습니다:
1. 어떤 사람이 함수 시그니쳐(매개변수의 타입, 반환값의 타입 등)를 볼 때, 어떤 속성이 사용되는지 즉시 알 수 있습니다.
2. 명명된 매개변수처럼 보이게 할 때 사용할 수 있습니다.
3. 또한 구조 분해는 함수로 전달된 매개변수 객체의 특정한 원시 값을 복제하며 이것은 사이드 이펙트를 방지하는데 도움을 줍니다. 유의사항: 매개변수 객체로부터 구조 분해된 객체와 배열은 **복제되지 않습니다.**
4. 타입스크립트는 사용하지 않은 속성에 대해서 경고를 주며, 구조 분해를 사용하면 경고를 받지 않을 수 있습니다.
**Bad:**
```ts
function createMenu(title: string, body: string, buttonText: string, cancellable: boolean) {
// ...
}
createMenu('Foo', 'Bar', 'Baz', true);
```
**Good:**
```ts
function createMenu(options: { title: string, body: string, buttonText: string, cancellable: boolean }) {
// ...
}
createMenu({
title: 'Foo',
body: 'Bar',
buttonText: 'Baz',
cancellable: true
});
```
[타입 앨리어스](https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-aliases)를 사용해서 가독성을 더 높일 수 있습니다:
```ts
type MenuOptions = { title: string, body: string, buttonText: string, cancellable: boolean };
function createMenu(options: MenuOptions) {
// ...
}
createMenu({
title: 'Foo',
body: 'Bar',
buttonText: 'Baz',
cancellable: true
});
```
**[⬆ 맨 위로 이동](#목차)**
### 함수는 한 가지만 해야합니다
이것은 소프트웨어 공학에서 단연코 가장 중요한 규칙입니다. 함수가 한 가지 이상의 역할을 수행할 때 작성하고 테스트하고 추론하기 어려워집니다. 함수를 하나의 행동으로 정의할 수 있을 때, 쉽게 리팩토링할 수 있으며 코드를 더욱 명료하게 읽을 수 있습니다.
이 가이드에서 이 부분만 자기것으로 만들어도 당신은 많은 개발자보다 앞설 수 있습니다.
**Bad:**
```ts
function emailClients(clients: Client[]) {
clients.forEach((client) => {
const clientRecord = database.lookup(client);
if (clientRecord.isActive()) {
email(client);
}
});
}
```
**Good:**
```ts
function emailClients(clients: Client[]) {
clients.filter(isActiveClient).forEach(email);
}
function isActiveClient(client: Client) {
const clientRecord = database.lookup(client);
return clientRecord.isActive();
}
```
**[⬆ 맨 위로 이동](#목차)**
### 함수가 무엇을 하는지 알 수 있도록 함수 이름을 지으세요
**Bad:**
```ts
function addToDate(date: Date, month: number): Date {
// ...
}
const date = new Date();
// 무엇이 추가되는지 함수 이름만으로 유추하기 어렵습니다
addToDate(date, 1);
```
**Good:**
```ts
function addMonthToDate(date: Date, month: number): Date {
// ...
}
const date = new Date();
addMonthToDate(date, 1);
```
**[⬆ 맨 위로 이동](#목차)**
### 함수는 단일 행동을 추상화해야 합니다
함수가 한 가지 이상을 추상화한다면 그 함수는 너무 많은 일을 하게 됩니다. 재사용성과 쉬운 테스트를 위해서 함수를 쪼개세요.
**Bad:**
```ts
function parseCode(code: string) {
const REGEXES = [ /* ... */ ];
const statements = code.split(' ');
const tokens = [];
REGEXES.forEach((regex) => {
statements.forEach((statement) => {
// ...
});
});
const ast = [];
tokens.forEach((token) => {
// lex...
});
ast.forEach((node) => {
// parse...
});
}
```
**Good:**
```ts
const REGEXES = [ /* ... */ ];
function parseCode(code: string) {
const tokens = tokenize(code);
const syntaxTree = parse(tokens);
syntaxTree.forEach((node) => {
// parse...
});
}
function tokenize(code: string): Token[] {
const statements = code.split(' ');
const tokens: Token[] = [];
REGEXES.forEach((regex) => {
statements.forEach((statement) => {
tokens.push( /* ... */ );
});
});
return tokens;
}
function parse(tokens: Token[]): SyntaxTree {
const syntaxTree: SyntaxTree[] = [];
tokens.forEach((token) => {
syntaxTree.push( /* ... */ );
});
return syntaxTree;
}
```
**[⬆ 맨 위로 이동](#목차)**
### 중복된 코드를 제거해주세요
코드가 중복되지 않도록 최선을 다하세요.
중복된 코드는 어떤 로직을 변경할 때 한 곳 이상을 변경해야 하기 때문에 좋지 않습니다.
당신이 레스토랑을 운영하면서 재고를 추적한다고 상상해보세요: 모든 토마토, 양파, 마늘, 양념 등.
관리하는 목록이 여러개일 때 토마토를 넣은 요리를 제공할 때마다 모든 목록을 수정해야 합니다.
관리하는 목록이 단 하나일 때는 한 곳만 수정하면 됩니다!
당신은 종종 두 개 이상의 사소한 차이점이 존재한다고 생각해서 거의 비슷한 코드를 중복 작성합니다. 하지만 그 몇가지 다른 것으로 인해 같은 역할을 하는 두 개 이상의 함수를 만들게 됩니다. 중복된 코드를 제거하는 것은 조금씩 다른 역할을 하는 것을 묶음으로써 하나의 함수/모듈/클래스로 처리하는 추상화를 만드는 것을 의미합니다.
추상화를 올바르게 하는 것은 중요하며, 이것은 [SOLID](#solid) 원칙을 따르는 이유이기도 합니다. 올바르지 않은 추상화는 중복된 코드보다 나쁘므로 주의하세요! 좋은 추상화를 할 수 있다면 그렇게 하라는 말입니다! 반복하지 마세요. 그렇지 않으면 하나를 변경할 때마다 여러 곳을 변경하게 될 것입니다.
**Bad:**
```ts
function showDeveloperList(developers: Developer[]) {
developers.forEach((developer) => {
const expectedSalary = developer.calculateExpectedSalary();
const experience = developer.getExperience();
const githubLink = developer.getGithubLink();
const data = {
expectedSalary,
experience,
githubLink
};
render(data);
});
}
function showManagerList(managers: Manager[]) {
managers.forEach((manager) => {
const expectedSalary = manager.calculateExpectedSalary();
const experience = manager.getExperience();
const portfolio = manager.getMBAProjects();
const data = {
expectedSalary,
experience,
portfolio
};
render(data);
});
}
```
**Good:**
```ts
class Developer {
// ...
getExtraDetails() {
return {
githubLink: this.githubLink,
}
}
}
class Manager {
// ...
getExtraDetails() {
return {
portfolio: this.portfolio,
}
}
}
function showEmployeeList(employee: Developer | Manager) {
employee.forEach((employee) => {
const expectedSalary = employee.calculateExpectedSalary();
const experience = employee.getExperience();
const extra = employee.getExtraDetails();
const data = {
expectedSalary,
experience,
extra,
};
render(data);
});
}
```
당신은 중복된 코드에 대해서 비판적으로 생각해야 합니다. 가끔은 중복된 코드와 불필요한 추상화로 인한 복잡성 간의 맞바꿈이 있을 수 있습니다. 서로 다른 두 개의 모듈의 구현이 유사해 보이지만 서로 다른 도메인에 존재하는 경우, 코드 중복은 공통된 코드에서 추출해서 중복을 줄이는 것보다 나은 선택일 수 있습니다. 이 경우에 추출된 공통의 코드는 두 모듈 사이에서 간접적인 의존성이 나타나게 됩니다.
**[⬆ 맨 위로 이동](#목차)**
### `Object.assign` 혹은 구조 분해를 사용해서 기본 객체를 만드세요
**Bad:**
```ts
type MenuConfig = { title?: string, body?: string, buttonText?: string, cancellable?: boolean };
function createMenu(config: MenuConfig) {
config.title = config.title || 'Foo';
config.body = config.body || 'Bar';
config.buttonText = config.buttonText || 'Baz';
config.cancellable = config.cancellable !== undefined ? config.cancellable : true;
// ...
}
createMenu({ body: 'Bar' });
```
**Good:**
```ts
type MenuConfig = { title?: string, body?: string, buttonText?: string, cancellable?: boolean };
function createMenu(config: MenuConfig) {
const menuConfig = Object.assign({
title: 'Foo',
body: 'Bar',
buttonText: 'Baz',
cancellable: true
}, config);
// ...
}
createMenu({ body: 'Bar' });
```
대안으로, 기본 값을 구조 분해를 사용해서 해결할 수 있습니다:
```ts
type MenuConfig = { title?: string, body?: string, buttonText?: string, cancellable?: boolean };
function createMenu({ title = 'Foo', body = 'Bar', buttonText = 'Baz', cancellable = true }: MenuConfig) {
// ...
}
createMenu({ body: 'Bar' });
```
사이드 이펙트와 `undefined` 혹은 `null` 값을 명시적으로 넘기는 예상치 못한 행동을 피하기 위해서 타입스크립트 컴파일러에게 그것을 허락하지 않도록 설정할 수 있습니다. 타입스크립트에서 [`--strictNullChecks`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#--strictnullchecks) 옵션을 확인하세요.
**[⬆ 맨 위로 이동](#목차)**
### 함수 매개변수로 플래그를 사용하지 마세요
플래그를 사용하는 것은 해당 함수가 한 가지 이상의 일을 한다는 것을 뜻합니다.
함수는 한 가지의 일을 해야합니다. boolean 변수로 인해 다른 코드가 실행된다면 그 함수를 쪼개도록 하세요.
**Bad:**
```ts
function createFile(name: string, temp: boolean) {
if (temp) {
fs.create(`./temp/${name}`);
} else {
fs.create(name);
}
}
```
**Good:**
```ts
function createTempFile(name: string) {
createFile(`./temp/${name}`);
}
function createFile(name: string) {
fs.create(name);
}
```
**[⬆ 맨 위로 이동](#목차)**
### 사이드 이펙트를 피하세요 (파트 1)
함수는 값을 가져와서 다른 값을 반환하는 것 이외에 다른 것을 할 경우 사이드 이펙트를 발생시킬 수 있습니다. 사이드 이펙트는 파일을 쓴다거나, 전역 변수를 조작한다거나, 뜻하지 않게 낯선 사람에게 당신의 전재산을 송금할 수 있습니다.
당신은 가끔 프로그램에서 사이드 이펙트를 가질 필요가 있습니다. 이전의 사례에서와 같이 당신은 파일을 써야할 때가 있습니다.
당신이 하고 싶은 것은 이것을 중앙화하는 것입니다. 특정 파일을 쓰기 위해 몇 개의 함수와 클래스를 만들지 마세요.
그것을 행하는 서비스를 단 하나만 만드세요.
중요한 것은 어떠한 구조도 없이 객체 사이에 상태를 공유하거나 어떤 것에 의해서든지 변경될 수 있는 데이터 타입을 사용하거나 사이드 이펙트가 일어나는 곳을 중앙화 하지 않는 것과 같은 위험 요소를 피하는 것입니다. 만약 그렇게 할 수 있다면, 당신은 대부분의 다른 프로그래머들보다 더욱 행복할 것입니다.
**Bad:**
```ts
// 아래의 함수에서 참조하는 전역 변수입니다.
let name = 'Robert C. Martin';
function toBase64() {
name = btoa(name);
}
toBase64();
// 이 이름을 사용하는 다른 함수가 있다면, 그것은 Base64 값을 반환할 것입니다
console.log(name); // 'Robert C. Martin'이 출력되는 것을 예상했지만 'Um9iZXJ0IEMuIE1hcnRpbg=='가 출력됨
```
**Good:**
```ts
const name = 'Robert C. Martin';
function toBase64(text: string): string {
return btoa(text);
}
const encodedName = toBase64(name);
console.log(name);
```
**[⬆ 맨 위로 이동](#목차)**
### 사이드 이펙트를 피하세요 (파트 2)
자바스크립트에서 원시값은 값에 의해 전달되고 객체/배열은 참조에 의해 전달됩니다. 예를 들어, 객체와 배열의 경우 어떤 함수가 쇼핑 장바구니 배열을 변경하는 기능을 가지고 있다면, 구매하려는 아이템이 추가됨으로써 `cart` 배열을 사용하는 다른 함수는 이 추가의 영향을 받을 수 있습니다. 이것은 장점이 될 수도 있지만 단점이 될 수도 있습니다. 최악의 상황을 상상해보겠습니다:
사용자는 네트워크 요청을 생성하고 서버에 `cart` 배열을 전송하는 `purchase` 함수를 호출하는 "구매" 버튼을 클릭합니다. 네트워크 연결 불량 때문에 `purchase` 함수는 요청을 재시도해야 합니다. 네트워크 요청이 시작되기 전에 사용자가 원하지 않은 아이템을 실수로 "장바구니에 추가하기" 버튼을 누르면 어떻게 될까요? 네트워크 요청이 시작되면, `purchase` 함수는 `addItemToCart` 함수가 변경한 쇼핑 장바구니 배열을 참조하고 있기 때문에 `purchase` 함수는 실수로 추가된 아이템을 보낼 것입니다.
훌륭한 해법은 `addItemToCart` 함수에서 `cart` 배열을 복제하고 그것을 수정하고 그 복제한 값을 반환하는 것입니다. 이는 쇼핑 장바구니 배열을 참조하고 있는 값을 들고 있는 어떤 다른 함수도 다른 변경에 의해 영향을 받지 않는 것을 보장합니다.
이 접근법에 대한 두 가지 주의사항:
1. 실제로는 입력된 객체값을 변경하기를 원하는 경우가 있을 수 있습니다. 하지만 이러한 프로그래밍 관례를 선택할 때 당신은 이러한 경우가 매우 드물다는 것을 알게 될 것입니다. 대부분은 사이드 이펙트가 없도록 리팩토링될 수 있습니다! ([순수 함수](https://en.wikipedia.org/wiki/Pure_function)를 확인해주세요)
3. 큰 객체를 복제하는 것은 성능 관점에서 비용이 높을 수 있습니다. 다행히도 이러한 프로그래밍 접근법을 가능하게 해주는 훌륭한 라이브러리가 있기 때문에 큰 문제는 아닙니다. 이는 수동으로 객체와 배열을 복제해주는 것만큼 메모리 집약적이지 않게 해주고 빠르게 복제해줍니다.
**Bad:**
```ts
function addItemToCart(cart: CartItem[], item: Item): void {
cart.push({ item, date: Date.now() });
};
```
**Good:**
```ts
function addItemToCart(cart: CartItem[], item: Item): CartItem[] {
return [...cart, { item, date: Date.now() }];
};
```
**[⬆ 맨 위로 이동](#목차)**
### 전역 함수를 작성하지 마세요
전역을 더럽히는 것은 자바스크립트에서 나쁜 관습입니다. 왜냐하면 다른 라이브러리와 충돌날 수 있고 당신의 API의 사용자는 상용에서 예외가 발생할 때까지 전혀 모를 것이기 때문입니다. 한 예제를 생각해보겠습니다: 당신이 자바스크립트 네이티브 배열 메소드를 확장해서 두 배열 사이의 다른 점을 보여주는 `diff` 메소드를 추가하고 싶다면 어떨까요? `Array.prototype`에 당신의 새로운 함수를 작성할 것입니다. 하지만 동일한 기능을 수행하고 있는 다른 라이브러리와 충돌날 수 있습니다. 다른 라이브러리에서는 배열에서 첫 번째 요소와 마지막 요소 사이의 다름만 찾기 위해 `diff` 함수를 사용한다면 어떨까요? 이것이 단지 클래스를 사용해서 전역 `Array`를 상속하는 것이 더 좋은 이유입니다.
**Bad:**
```ts
declare global {
interface Array<T> {
diff(other: T[]): Array<T>;
}
}
if (!Array.prototype.diff) {
Array.prototype.diff = function <T>(other: T[]): T[] {
const hash = new Set(other);
return this.filter(elem => !hash.has(elem));
};
}
```
**Good:**
```ts
class MyArray<T> extends Array<T> {
diff(other: T[]): T[] {
const hash = new Set(other);
return this.filter(elem => !hash.has(elem));
};
}
```
**[⬆ 맨 위로 이동](#목차)**
### 명령형 프로그래밍보다 함수형 프로그래밍을 지향하세요
가능하다면 이런 방식의 프로그래밍을 지향하세요.
**Bad:**
```ts
const contributions = [
{
name: 'Uncle Bobby',
linesOfCode: 500
}, {
name: 'Suzie Q',
linesOfCode: 1500
}, {
name: 'Jimmy Gosling',
linesOfCode: 150
}, {
name: 'Gracie Hopper',
linesOfCode: 1000
}
];
let totalOutput = 0;
for (let i = 0; i < contributions.length; i++) {
totalOutput += contributions[i].linesOfCode;
}
```
**Good:**
```ts
const contributions = [
{
name: 'Uncle Bobby',
linesOfCode: 500
}, {
name: 'Suzie Q',
linesOfCode: 1500
}, {
name: 'Jimmy Gosling',
linesOfCode: 150
}, {
name: 'Gracie Hopper',
linesOfCode: 1000
}
];
const totalOutput = contributions
.reduce((totalLines, output) => totalLines + output.linesOfCode, 0);
```
**[⬆ 맨 위로 이동](#목차)**
### 조건문을 캡슐화하세요
**Bad:**
```ts
if (subscription.isTrial || account.balance > 0) {
// ...
}
```
**Good:**
```ts
function canActivateService(subscription: Subscription, account: Account) {
return subscription.isTrial || account.balance > 0;
}
if (canActivateService(subscription, account)) {
// ...
}
```
**[⬆ 맨 위로 이동](#목차)**
### 부정 조건문을 피하세요
**Bad:**
```ts
function isEmailNotUsed(email: string): boolean {
// ...
}
if (isEmailNotUsed(email)) {
// ...
}
```
**Good:**
```ts
function isEmailUsed(email): boolean {
// ...
}
if (!isEmailUsed(node)) {
// ...
}
```
**[⬆ 맨 위로 이동](#목차)**
### 조건문을 피하세요
불가능해보일 수 있습니다. 처음 이를 본 대부분의 사람들은 "대체 `if`문 없이 뭘 할 수 있나요?" 라고 반응합니다. 하지만 많은 경우에 다형성을 사용한다면 해결할 수 있습니다. 그 다음 반응으로는 "좋아요. 하지만 왜 그래야하죠?" 입니다. 이에 대한 해답은 우리가 이전에 배운 클린 코드 컨셉 중 "함수는 한 가지 일만 해야합니다" 입니다. `if`문이 있는 클래스와 함수가 있다면, 그 함수는 한 가지 이상의 일을 하고 있다는 것입니다. 함수는 한 가지 일만 해야한다는 것을 기억하세요.
**Bad:**
```ts
class Airplane {
private type: string;
// ...
getCruisingAltitude() {
switch (this.type) {
case '777':
return this.getMaxAltitude() - this.getPassengerCount();
case 'Air Force One':
return this.getMaxAltitude();
case 'Cessna':
return this.getMaxAltitude() - this.getFuelExpenditure();
default:
throw new Error('Unknown airplane type.');
}
}
private getMaxAltitude(): number {
// ...
}
}
```
**Good:**
```ts
abstract class Airplane {
protected getMaxAltitude(): number {
// shared logic with subclasses ...
}
// ...
}
class Boeing777 extends Airplane {
// ...
getCruisingAltitude() {
return this.getMaxAltitude() - this.getPassengerCount();
}
}
class AirForceOne extends Airplane {
// ...
getCruisingAltitude() {
return this.getMaxAltitude();
}
}
class Cessna extends Airplane {
// ...
getCruisingAltitude() {
return this.getMaxAltitude() - this.getFuelExpenditure();
}
}
```
**[⬆ 맨 위로 이동](#목차)**
### 타입 체킹을 피하세요
타입스크립트는 자바스크립트의 엄격한 구문적 상위 집합이며 언어에 선택적인 정적 타입 검사 기능을 추가합니다.
타입스크립트의 기능을 최대한 활용하기 위해 항상 변수의 타입, 매개변수, 반환값의 타입을 지정하도록 하세요.
그렇게 하면 리팩토링이 매우 쉬워집니다.
**Bad:**
```ts
function travelToTexas(vehicle: Bicycle | Car) {
if (vehicle instanceof Bicycle) {
vehicle.pedal(currentLocation, new Location('texas'));
} else if (vehicle instanceof Car) {
vehicle.drive(currentLocation, new Location('texas'));
}
}
```
**Good:**
```ts
type Vehicle = Bicycle | Car;
function travelToTexas(vehicle: Vehicle) {
vehicle.move(currentLocation, new Location('texas'));
}
```
**[⬆ 맨 위로 이동](#목차)**
### 필요 이상으로 최적화하지 마세요
현대 브라우저는 런타임에서 많은 최적화를 수행합니다. 많은 시간을 최적화하는 데에 사용하고 있다면 시간 낭비입니다. 최적화가 부족한 부분을 확인할 수 있는 좋은 [자료](https://github.com/petkaantonov/bluebird/wiki/Optimization-killers)가 있습니다. 이것을 참조하여 최적화가 부족한 부분만 최적화해줄 수 있습니다.
**Bad:**
```ts
// 예전 브라우저에서는 캐시되지 않은 `list.length`를 사용한 각 순회는 비용이 많이 들 것입니다.
// `list.length`의 재계산 때문입니다. 현대 브라우저에서는 이 부분이 최적화됩니다.
for (let i = 0, len = list.length; i < len; i++) {
// ...
}
```
**Good:**