안녕하세요 :)
이번에는 Clean Code라는 주제로 글을 작성해볼까 합니다~!
이 토픽을 검색하고, 제 게시글을 찾아주셨다는 것은 아마 현직자일 확률이 높으신데요 ㅎㅎ
요새는 취준생들도 많이 중요시하게 여기고 있는 항목이라
공부를 많이 하시는 분들도 제 글을 찾아주셨을거라 생각이 들어요!
그래서 최대한 쉽게 설명해볼까 합니다 ㅎㅎ
다들 따뜻한 아메리카노 준비되셨나요~?
그럼 출발할게요!! ^-^
Clean Code
클린코드가 뭘까요?
제가 아는 주위 사람들에게 물어봐도
제마다 Clean Code의 정의를 정말 다르게 말씀하시더라구요
어떤 사람은
읽기편한 코드가 클린코드다.
어떤 사람은
개방성에 확장되어 있는 코드가 클린코드다.
어떤 사람은
객체지향적으로 작성한 코드가 클린코드다.
정말 다들 맞는 말씀들이세요
위에 말씀하신 것들이 정말 중요한 원칙들이고, Clean Code를 위해서는 꼭 있어야 될 항목들이죠
하지만
제가 생각하는 클린코드는
- 읽기 편하고
- 확장성에 열려 있고
- 테스트 코드가 알찬
코드가 클린코드 라고 생각합니다!
제가 너무 양아치 같았나요?
다들 말씀해주신 정의들이 정말 옳아요.
다만 저는 한가지만으로 딱 정의될 수 없다고 생각했어요
Clean Code라는 것은 수많은 Code Style들의 결정체라고 생각하거든요
이제 제가 생각한 요인들을 바탕으로
하나씩 설명해드릴까 해요 ㅎㅎ
읽기 편한 코드
다들 백준에서 알고리즘 문제 많이 풀어봤나요?
풀어보시면서...
그때 본인들이 작성한 코드를 남에게 설명해 보신 적이 있으신가요?
설명하면서 내가 작성한 코드가 정말 개판이라고 느껴본적 없으신가요?
남들이 이해하기가 어렵다고 생각해보신적 없으신가요?
왜 이러한 생각들을 가지게 되었을까요?
그냥 문제만 풀면 되는 Code기 때문이죠 = 대충 작성해서 돌아가면 된다
실제로 제가 풀이한 코드 예시를 가져올게요
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int k = sc.nextInt();
int ans = -1;
int MAX = 500001;
int [][]d = new int[MAX][2];
Queue<Integer> q = new LinkedList<>();
q.add(n);
q.add(0);
q.add(0);
while(!q.isEmpty()) {
int x = q.poll();
int t = q.poll();
int nt = q.poll();
for(int next : new int[] {x+1, x-1, x*2}) {
if(next >= 0 && next < MAX && d[next][1-t] == 0) {
d[next][1-t] = nt+1;
q.add(next);
q.add(1-t);
q.add(nt+1);
}
}
}
if(n==k) {
System.out.println(0);
return;
}
for(int i=0; ; i++) {
k += i;
if(k >= MAX) break;
if(d[k][i%2] != 0 && d[k][i%2] <= i) {
ans = i;
break;
}
}
System.out.println(ans);
}
}
백준의 17071_숨바꼭질5라는 문제인데요
와후.......
실제로 이렇게 작성해서 리뷰받으면 욕을 엄청 먹을 것 같아요
위 Code의 부족 요인이 무엇일까요?
- 알 수 없는 매직넘버들 ex) 0, 2 ....
- 의미를 알 수 없는 변수 네이밍 ex) x, t, nt, k
- main 함수에 모든 것을 작성 = 모듈화 부족 = 개방성에 닫혀있음
근데 정말로
대다수의 학생들은 실제로 Code를 이렇게 작성하고 계세요
프로젝트나 과제를 할 때도 그냥 코드가 돌아가기만 하면 된다 라고 생각하시죠..
이렇게 작성하시면... 협업할 때 정말 힘들어져요
원래 프로그래머는 내가 작성한 Code보다 남이 작성한 Code 보는게 더 힘든 일인데
이렇게 해버리면
너무 배려심이 없는거죠
그럼 어떻게 해볼까요?
예시를 또 가져왔죠 ㅎㅎ
왼쪽과 오른쪽의 차이가 무엇일까요?
- 의미 있는 네이밍 flaggedCells ↔ list1
- 의미 있는 조건문 (x[0] == 4) ↔ (cell.isFlagged())
- 매직 넘버 삭제
왼쪽은 작성자에게 설명을 듣지 못하면 이해가 전혀 안되지만,
오른쪽은 읽기만 해도 벌써 구조가 이해되지 않으신가요?ㅎㅎ
이래서 읽기 편한 코드를 작성해야 되는 겁니다
매번 동료 프로그래머에게 물어보고 작업할 순 없으니까요~
확장성에 열려 있는 코드
확장성에 열려 있는 코드는
정말 심플하게도 한 문장으로 요약되요!
SOLID 원칙을 준수한 코드
확장성에 대한 설명은 제가 ISP 설명한 게시글 에 나타나 있어요~!
그래도 다시 한번 요약해볼께요
WAS(Web Application Server) 를 직접 구현하실 때
Front-End 에 Button을 하나 추가했을뿐인데, Back-end를 싹 다 갈아 엎는 상황이 발생한적 있으신가요?
도대체 왜 그럴까요?
Back-end Code를 정말 더럽게 작성했기 때문이에요
사람들의 요구사항은 끊임없이 변경되고,
우리가 작성한 코드는 계속 바뀌어야 해요~!
실용주의 프로그래머 라는 책에서도 나와 있듯이
내가 작성한 코드가 완성본이라는 생각은 하지 말자.
꼭 꼭 중요합니다~!
테스트 코드가 알찬 코드
켄트 백의 TDD(Test Driven Development) 이론이 정말 많이 떠오르고 있죠
그럼 왜 TDD를 우리는 실행해야 할까요?
Test Code가 없는 Production Code는 불완전하고, 기능이 추가될 때 마다 버그가 어디서 발생될지 모르기 때문이죠
또, 제가 작성한 코드를 다시 꺼내볼게요
package solid.srp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Student {
private List<Course> courses;
public Student() {
courses = new ArrayList<>();
}
public void chooseCourse(final Course course) {
courses.add(course);
}
public void modifyCourse(final Course course, final String courseName) {
courses.stream()
.filter(schoolCourse -> schoolCourse.equals(course))
.forEach(schoolCourse -> schoolCourse.setName(courseName));
}
public void modifyCourse(final Course course, final Course modifidedCourse) {
courses.remove(course);
courses.add(modifidedCourse);
}
public List<Course> getCourses() {
return Collections.unmodifiableList(courses);
}
}
학생이 수강과목을 추가하고 변경하는 코드입니다~!
우리가 생각한대로 잘 작성했겠죠
절대로 아닙니다
하지만 검증에 대한 안전성이 보장되었을까요?
혹시나 Course 라는 객체의 정의가 바뀐다면?
혹시나 Course 를 삭제하는 기능을 추가한다면?
코드가 변경되도 안정성이 유지될까요?
그래서, Test Code를 작성하는 것입니다!
package solid.srp;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
class StudentTest {
@Test
void 학생이_과목을_올바르게_변경하는지_테스트_1번_케이스() {
/* Given */
Student student = new Student();
Course math = new Course("math");
Course english = new Course("english");
Course computer = new Course("computer");
List<Course> courses = Arrays.asList(math, computer);
/* When */
student.chooseCourse(math);
student.chooseCourse(english);
student.modifyCourse(english, "computer");
/* Then */
assertThat(student.getCourses()).isEqualTo(courses);
assertThat(english.getName()).isEqualTo("english");
}
@Test
void 학생이_과목을_올바르게_변경하는지_테스트_2번_케이스() {
/* Given */
Student student = new Student();
Course math = new Course("math");
Course english = new Course("english");
Course computer = new Course("computer");
List<Course> courses = Arrays.asList(math, computer);
/* When */
student.chooseCourse(math);
student.chooseCourse(english);
student.modifyCourse(english, computer);
/* Then */
assertThat(student.getCourses()).isEqualTo(courses);
assertThat(english.getName()).isEqualTo("english");
}
}
다만 켄트백이 말씀하신 것처럼 우리는 TDD방식으로 작성해야 됩니다.
- Test Code를 먼저 작성하고
- Production Code가 컴파일만 되게 작성한 뒤
- 테스트 실시 후, 틀리는 Test Case에 대하여 Production Code 리팩토링
그 후는 1~3 과정을 반복주기로 재시행
반드시 Test Code가 있어야 Production Code가 안정해집니다!
또한 Test Code도 Production Code를 작성하는 것처럼 노력을 기울여야 해요!!
어떤 것을 테스트할 것인지?
끊임없이 생각해보아야 합니다
경계값 체크, 다양한 Case 작성 은 덤이구요 ^-^
Clean Coder
제가 생각하기에는 Clean Code는 문화라고 생각합니다
여러분이 속한 조직에서 문화가 정착되지 않는다면 본래 습관대로 코드를 작성하기 일상이에요
여러분이 속한 조직을 바꿔보심을 어떠신가요?
스터디나 세미나를 통해 Clean Code의 중요성을 강조하고 문화를 선도해보세요
처음에는 힘들고 어렵겠지만, 해보시면 정말 프로젝트할 때 편리하실 거에요
우리가 처음에 코딩 배울때 굉장히 높은 장벽이 있었죠?
printf() scanf()도 어려웠었지만, 지금은 어떤가요?
너무 쉽죠?
Clean Code도 마찬가지에요.
처음에는 정말 고달프고 힘들거에요.
다만 익숙해지면 정말 편리하고 깨끗한 문화가 될 것이라 생각하면서
글을 마칩니다 ㅎㅎ
제 글이 도움이 되셨다면, 언제든 다시 찾아와주세요~!!
참고
Clean Code
'Developer > 개발서적' 카테고리의 다른 글
오브젝트(조영호) 책 후기 - 객체지향설계의 끝은 어디인가 (1) | 2020.01.07 |
---|---|
Clean Code(클린코드) - 우리가 코드를 깨끗하게 작성해야 하는 이유 : (2) 테스트 코드도 코드다 (0) | 2019.12.17 |
Clean Code(클린코드) - 우리가 코드를 깨끗하게 작성해야 하는 이유 : (1) 함수는 한가지 일만 제대로 하게 하라 (0) | 2019.12.11 |
개발서적 - 객체지향의 사실과 오해 (0) | 2019.05.17 |