나쁜 코드
- 깨진 유리창 법칙
💡실용주의 프로그래머 데이브 토마스 & 앤디 헌트의 '깨진 창문'
창문이 깨진 건물은 누구도 상관하지 않는다는 인상을 풍겨 사람들이 관심을 끊고 창문이 더 깨져도 상관하지 않는다. 따라서 창문이 깨지고 나면 쇠퇴하는 과정이 시작되는데, 나쁜 코드도 깨진 유리창처럼 나쁜 코드는 오히려 더나쁜 코드를 만든다는 비유이다.
- 생산성 저하
나쁜 코드는 팀 생산성을 저하시켜 기술부채를 만들어 수정을 더 어렵게 한다. 코드를 고칠 때마다 엉뚱한 곳에서 문제가 생기고 간단한 변경이 불가능하니, 얽히고설킨 코드를 더한다. 코드가 더해질수록 진행 속도는 더뎌지고 복구도 어려워진다.
- 새로운 시스템
기존 시스템을 모두 유지보수하며 이를 대체할 새로운 시스템 개발은 현실적으로 매우 어렵다. 이 말은 즉슨, 기존 시스템 기능을 모두 제공하고 기존 시스템에 발생한 변경도 새 시스템에 반영되어야 한다는 뜻이다. 기존 시스템 기능을 100% 제공하지 않는 한, 새로운 시스템은 기존 시스템을 대체할 수 없기 때문이다.
- 나쁜 코드가 발생하는 이유
대부분 개발 일정을 맞추기 위해서이다. 하지만 나쁜 코드는 생산성을 저하하기 때문에 오히려 일정을 못 맞춘다.
또한 영향 범위가 넓은 코드는 다른 부분에 버그가 생길 것이라는 공포심을 불러오기 때문에 그대로 방치한다. 그러나 기술부채는 부메랑처럼 돌아온다는 것을 인지해야 한다.
성능이 나쁜 코드
- 불필요한 연산이 들어가 개선의 여지가 있는 코드
- 자원을 낭비하는 코드
의미가 모호한 코드
- 이해가 어려운 코드
- 네이밍과 그 내용이 다른 코드
중복된 코드
- 비슷한 내용의 중복되는 코드
클린 코드
나는 우아하고 효율적인 코드를 좋아한다. 논리가 간단해야 버그가 숨어들지 못한다.
의존성을 최대한 줄여야 유지보수가 쉬워진다. 오류는 명백한 전략에 의거해 철저히 처리한다.
성능을 최적으로 유지해야 사람들이 원칙 없는 최적화로 코드를 망치려는 유혹에 빠지지 않는다.
깨끗한 코드는 한 가지를 제대로 한다.
비야네 스트롭스트룹 - C++ 창시자
- 여기서 강조된 부분은 SRP 법칙과도 연관된다. SRP(Single Responsibility Principle)는 SOLID 원칙 중 하나로, "단일 책임 원칙"을 의미한다. 이 원칙의 핵심 아이디어는 한 클래스는 하나의 책임만 가져야 한다는 것이다. 여기서 "책임"이란 한 클래스가 변경되는 이유를 의미한다.
<나쁜 코드 예>
class Report {
String title;
String content;
void print() {
System.out.println("Printing report");
// 여기에 출력 로직
}
void saveToFile(String path) {
System.out.println("Saving report to file");
// 여기에 파일 저장 로직
}
}
- Report 클래스는 레포트를 표시하고 파일로 저장하는 두 가지 책임을 가지고 있다. 만약 파일 저장 방식이나 출력 방식이 변경된다면, Report 클래스를 수정해야 한다.
<좋은 코드 예>
class Report {
String title;
String content;
}
class ReportPrinter {
void print(Report report) {
System.out.println("Printing report");
// 여기에 출력 로직
}
}
class ReportSaver {
void saveToFile(Report report, String path) {
System.out.println("Saving report to file");
// 여기에 파일 저장 로직
}
}
- 각 클래스는 단일 책임을 가지고 있다. Report는 데이터를 저장하고, ReportPrinter는 레포트를 출력하며, ReportSaver는 레포트를 파일로 저장한다. 이로 인해 각 부분을 독립적으로 변경하거나 재사용할 수 있다.
- SRP를 준수함으로써 코드의 유지보수성, 이해하기 쉬움, 재사용성이 향상되는 이점을 얻을 수 있다.
깨끗한 코드는 단순하고 직접적이다. 깨끗한 코드는 잘 쓴 문장처럼 읽힌다. 깨끗한 코드는 결코 설계자의 의도를 숨기지 않는다. 오히려 명쾌한 추상화와 단순한 제어문으로 가득하다.
그래디 부치 - 객체지향 원리와 디자인 저자
성능이 좋은 코드
의미가 명확한 코드
- 가독성이 좋은 코드를 의미한다.
중복이 제거된 코드
의미 있는 이름 짓기
- 의미가 분명한 이름 짓기
<나쁜 네이밍 예>
- 변수
int a = 10; // 나쁜 예: 'a'라는 이름만으로는 변수의 역할이나 의미를 알 수 없음
String n = "John Doe"; // 나쁜 예: 'n'이 무엇을 의미하는지 알기 어려움
List<String> list = new ArrayList<>(); // 나쁜 예: 'list'는 리스트의 구체적인 내용을 알려주지 않음
- 클래스
class DataManager { // 나쁜 예: 너무 포괄적이고 구체적이지 않음. 데이터를 관리하는 것이지만 어떤 데이터를 어떻게 관리하는지 알 수 없음
// 클래스 내용
}
< 좋은 네이밍 예>
- 변수
int age = 25; // 좋은 예: 변수의 용도가 명확함
String fullName = "John Doe"; // 좋은 예: 전체 이름을 저장한다는 것이 명확함
List<String> customerNames = new ArrayList<>(); // 좋은 예: 고객의 이름을 저장하는 리스트임을 알 수 있음
- 클래스
class CustomerDatabaseManager { // 좋은 예: 고객 데이터를 관리하는 클래스라는 것이 명확함
// 클래스 내용
}
- 루프 속 i, j, k 사용하지 않기
배열 순회 시, advanced for 문이라 불리는 for-each 구문을 사용하면 배열이나 컬렉션을 더 쉽고 가독성 있게 순회할 수 있다.
String[] fruits = {"Apple", "Banana", "Cherry"};
for (String fruit : fruits) {
System.out.println(fruit);
}
for-each 구문은 배열뿐만 아니라 Iterable 인터페이스를 구현하는 모든 Java 컬렉션 타입에 사용할 수 있다. 예를 들어 List, Set 등의 컬렉션을 순회할 때도 이 구문을 사용할 수 있다.
또한 Java 8부터 람다 표현식을 사용하여 컬렉션을 더 간결하고 함수형 스타일로 처리할 수 있다. 이를 위해 Stream API를 사용할 수 있고, forEach 메소드를 사용하여 각 요소를 순회할 수 있다.
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
/*
stream() 메소드를 호출하여 컬렉션을 스트림으로 변환합니다.
forEach 메소드를 사용하여 각 요소를 출력합니다.
*/
List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry");
fruits.stream().forEach(fruit -> System.out.println(fruit));
}
}
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Main {
//filter와 map 사용
/*
filter 메소드를 사용하여 "A"로 시작하는 과일만 필터링합니다.
map 메소드를 사용하여 각 문자열을 대문자로 변환합니다.
collect 메소드와 Collectors.toList()를 사용하여 스트림을 다시 리스트로 변환합니다.
*/
public static void main(String[] args) {
List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry");
List<String> filteredFruits = fruits.stream()
.filter(fruit -> fruit.startsWith("A")) // "A"로 시작하는 과일을 필터링
.map(String::toUpperCase) // 모든 문자를 대문자로 변환
.collect(Collectors.toList()); // 결과를 다시 리스트로 변환
System.out.println(filteredFruits); // [APPLE] 출력
}
}
i, j, k 대신 맥락에 맞는 이름을 사용하는 것이 좋다.
- 통일성 있는 단어 사용하기
Member/Customer/User
Service/Manager
Repository/DAO
...
위 예시들은 프로젝트 진행을 하며 혼란을 일으킬 수 있는 네이밍이다. 결국 가르키는 대상은 같지만 이름이 다르기 때문이다. 따라서 팀원들과의 합으로 같은 이름을 사용해야 한다.
- 변수명에 타입 넣지 않기
변수명에 타입을 포함하는 것은 안티 패턴으로 간주된다. 이러한 스타일은 헝가리안 표기법이라고 불리며, 변수의 타입이나 다른 메타데이터 정보를 변수명에 직접 포함시킨다. 하지만 현대의 프로그래밍 언어와 개발 환경은 타입 정보를 명확하게 보여주기 때문에, 이러한 표기법을 사용할 필요가 없어졌다.
String strName = "John Doe";
int intAge = 30;
List<String> listFruits = Arrays.asList("Apple", "Banana", "Cherry");
//좋은 예
String name = "John Doe";
int age = 30;
List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry");
Google Java Naming Guide
https://google.github.io/styleguide/javaguide.html
Google Java Style Guide
1 Introduction This document serves as the complete definition of Google's coding standards for source code in the Java™ Programming Language. A Java source file is described as being in Google Style if and only if it adheres to the rules herein. Like ot
google.github.io
- Package Naming Guide
All lower case, no underscores
- Class Naming Guide
UpperCamelCase(대문자로 시작)
클래스는 명사, 명사구
Customer
인터페이스는 명사, 명사구, (형용사)
List, Readable
테스트 클래스는 Test로 끝나기
- Method Naming Guide
LowerCamelCase(소문자로 시작)
메서드는 동사, 동사구
jUnit 테스트에 underscore 사용되기도 함. <methodUnderTest>_<state>패턴