1. 컨벤션 꼭 지켜야 하나요?
흑백 요리사에 팀전을 보셨나요?
여러팀 중 가장 인상적으로 본 팀은 팀 트리플스타였습니다. 각각 다른 스타일의 고수들이 모였음에도 자신의 스타일을 고수할 수 있었지만, 최종적으로 하나의 요리를 완성하기 위해 하나되어 노력하는 모습이 참 이상적인 팀이라고 생각했습니다.
이상적인 팀엔 규칙과 시스템이 있습니다. 규칙을 준수하고 시스템에 녹아들어갈 때 트리플스타가 팀이 보여주었던 100인 만족하는 맛있는 요리(품질 높은 소프트웨어)를 만들 수 있게 됩니다.
Conding Convention은 기본기이며 요리사의 칼질과도 같은 것입니다!!
•
모든 셰프님들이 트리플스타를 입모아 칭찬한 이유가 무엇이었을까요?
1. 가독성 향상
코드 컨벤션을 지키면 코드를 읽고 이해하기 쉬워집니다. 일관된 들여쓰기, 변수 명명 규칙, 함수 정의 방식 등을 따르면, 다른 개발자가 코드를 보더라도 금방 이해할 수 있습니다.
2. 유지보수성 증가
컨벤션을 지키지 않으면 코드가 복잡하고 난잡해져, 시간이 지나면 수정하거나 기능을 추가하기가 어려워집니다. 이는 프로젝트가 커질수록 더 중요한 요소가 됩니다.
3. 협업 효율성 향상
여러 명이 함께 개발하는 프로젝트에서 각자가 제멋대로의 코딩 스타일을 사용하면 협업에 어려움이 생깁니다. 하지만 공통된 컨벤션을 따르면, 모든 팀원이 같은 방식으로 코드를 작성하게 되어 협업이 원활하게 이루어집니다.
4. 일관된 코드 스타일
컨벤션을 지키면 프로젝트 전반에서 코드 스타일이 일관되게 유지됩니다. 일관된 스타일은 코드를 읽는 사람에게 익숙함을 제공하며, 새로운 개발자가 프로젝트에 참여할 때도 쉽게 적응할 수 있게 도와줍니다.
5. 버그 발생 가능성 감소
명확하고 일관된 코딩 스타일을 사용하면 코드의 구조가 더 깔끔해지고, 실수나 버그가 발생할 가능성이 줄어듭니다. 특히, 변수 이름, 함수 이름 등을 명확하게 작성하면 코드의 의도를 파악하기 쉬워져 실수를 방지할 수 있습니다.
6. 코드 리뷰와 자동화 도구와의 연계
많은 자동화 도구(정적 분석기 - SonarQube 등)는 코드 컨벤션을 기반으로 코드 품질을 검사합니다. 일관된 컨벤션을 사용하면, 코드 리뷰와 코드 품질 검사 도구가 더 효과적으로 작동합니다. 컨벤션에 맞춘 코드 작성은 자동화된 코드 품질 체크 도구가 코드를 분석하고 문제를 찾아내는 데 유리합니다.
2. Coding Convention vs Clean Code
Coding Convention과 Clean Code는 모두 좋은 소프트웨어 개발을 위한 규칙과 원칙을 다루지만, 그 범위와 목적이 다릅니다. 차이를 아래와 같이 설명할 수 있습니다.
1. Coding Convention (코딩 컨벤션)
Coding Convention은 개발자가 따라야 할 일련의 규칙과 스타일 가이드입니다. 주로 코드의 형식과 구조를 표준화하는 데 중점을 두며, 팀 내에서 일관된 코딩 스타일을 유지하기 위한 규칙입니다. 코딩 컨벤션은 코드의 가독성과 유지보수성을 높이는 데 목적이 있습니다.
1.1 특징
•
코드의 형식을 규정:
◦
들여쓰기, 공백, 괄호 위치, 주석 스타일, 함수 및 변수 명명 규칙 등.
•
일관성 확보:
◦
같은 프로젝트 내 모든 코드가 동일한 스타일을 유지하여 코드 리뷰와 협업이 쉬워짐.
•
주로 도구에 의해 체크:
◦
Linter나 포매터 같은 자동화 도구로 컨벤션 준수 여부를 검사할 수 있음.
1.2 예시
•
자바 코딩 컨벤션:
◦
변수명은 소문자로 시작, 클래스명은 대문자로 시작.
◦
한 줄에 너무 많은 코드를 작성하지 않음.
◦
중괄호는 새로운 줄에 작성 (K&R 스타일).
코딩 컨벤션의 목적은 팀이 일관성 있게 코드를 작성하여 협업을 쉽게 하고, 유지보수와 코드 리뷰를 더 원활하게 만드는 데 있습니다.
2. Clean Code (클린 코드)
Clean Code는 코딩 컨벤션의 일부분을 포함하지만, 그보다 더 넓은 개념으로 코드의 품질과 설계 철학에 관한 것입니다. 가독성, 유지보수성, 효율성, 명료성을 모두 고려한 좋은 코드를 작성하는 데 중점을 둡니다. 단순히 형식적인 규칙을 따르는 것만이 아니라, 코드 자체가 직관적이고 명확하게 작성되도록 하는 것이 목표입니다.
2.1 특징
•
가독성: 다른 개발자가 코드를 쉽게 이해할 수 있어야 함.
•
간결성: 불필요한 코드나 복잡성을 제거하여, 코드는 간단하고 명확하게 유지.
•
함수 및 클래스 설계: 한 함수는 하나의 역할만 수행해야 하고, 클래스도 단일 책임 원칙(SRP)을 준수해야 함.
•
의도 전달: 함수나 변수의 이름만으로도 그 역할과 의도를 명확히 알 수 있어야 함.
•
주석 최소화: 코드 자체가 의도를 설명해야 하며, 불필요한 주석은 피함. 대신, 중요한 부분이나 복잡한 로직에만 주석을 사용.
2.2 예시
•
클린 코드 원칙 (로버트 C. 마틴의 Clean Code에서 제시된 예시):
◦
작고 단순한 함수: 하나의 함수는 한 가지 역할만 수행해야 함.
◦
명확한 변수명: var1, temp 같은 추상적 이름이 아니라 totalCost, userEmail 같은 의미 있는 이름을 사용.
◦
중복 제거: 중복된 코드를 줄이고, 코드의 재사용성을 높임.
◦
짧고 간결한 코드: 불필요한 조건문이나 반복문을 제거하고, 코드를 최대한 간결하게 유지.
클린 코드의 목적은 코드가 단순히 동작만 하는 것이 아니라, 코드 자체가 명확하고 직관적이며 유지보수하기 쉽도록 만드는 것입니다.
3. 차이점 요약
항목 | Coding Convention | Clean Code |
초점 | 코드의 형식과 구조 (스타일, 명명 규칙) | 코드의 품질과 설계 (가독성, 효율성) |
목적 | 일관된 코딩 스타일을 유지 | 코드의 직관성, 유지보수성, 재사용성 향상 |
주로 다루는 부분 | 들여쓰기, 공백, 변수/함수 이름, 주석 등 | 함수 설계, 명확한 이름, 단순한 구조 |
검사 방법 | Linter, 포매터 같은 자동화 도구 | 코드 리뷰, 소프트웨어 설계 원칙 |
팀 협업 | 코드 스타일을 통일하여 협업 효율성 향상 | 협업 시 코드를 쉽게 이해하고 유지보수 가능 |
4. 결론
•
Coding Convention은 일관된 코드 스타일을 유지하기 위한 형식적 규칙입니다.
•
Clean Code는 코드가 더 나은 설계와 가독성을 지향하도록 작성하는 철학입니다.
코딩 컨벤션은 클린 코드를 위한 첫걸음이라고 할 수 있으며, 두 개념은 서로 보완 관계에 있습니다. 즉, 컨벤션을 지키는 것은 클린 코드를 작성하는 데 중요한 기초가 됩니다.
3. 방법
Coding Convention은 조직마다 그 방식이 다릅니다. 흔히 알고 있는 Google, Apple, Naver, Kakao 회사마다 말이죠. 이번 시간에는 일반적인 Java 진형에서 요구하는 규칙에 대해서 설명하겠습니다.
// 계산기 Class를 만들어보았습니다. 무엇이 잘못 되었을까요?
public class cal{
private int NumberOne;
private int numberTwo;
public cal(int num1, int Num2){
NumberOne = num1;
numberTwo = Num2;
}
public void ADD(){
int result=NumberOne +numberTwo;
System.out.println(result);
}
public static void main(String[] args){
cal calc = new cal(5,10);
calc.ADD();
}
}
Java
복사
1. 네이밍
기본적으로 네이밍은 Full-Text 작성을 지향합니다.
다만, 조직마다 정의되어있는 단어사전(단어의 뜻을 정의하고 약어를 정리한 사전)이 있는 경우 사전에서 사용하고자 하는 단어를 찾아 네이밍합니다.
ex) name : 이름, 성명 → nm
ex) User : 사용자 → usr
ex) 사용자명 → usr + nm = usrNm
잠깐만! 표기법에 대해서 집고 넘어갑시다!
표기법 | 형식 | 예시 | 사용 예 |
Camel Case | 첫 단어는 소문자, 이후 단어는 대문자 | myVariableName | 변수/메소드 |
Pascal Case | 모든 단어의 첫 글자 대문자 | MyClassName | 클래스 |
Snake Case | 단어 사이에 _, 소문자 | my_variable_name | |
Upper Snake Case | 단어 사이에 _, 대문자 | MAX_VALUE | 상수 |
Kebab Case | 단어 사이에 -, 소문자 | my-variable-name | URL, CSS 클래스 |
1.1 패키지
// 패키지 이름은 소문자를 사용합니다. 단어별 구문을 위해 언더스코어(_)나 대문자를 포함하지 않습니다
// 좋은 예
package com.sparta.api
package com.sparta.userapi
// 나쁜 예
package com.sparta.user_api
package com.sparta.userApi
Java
복사
1.2 클래스 & 인터페이스
•
클래스 이름: PascalCase를 사용하여 작성하며, 일반적으로 명사로 표현합니다.
◦
예시: Customer, OrderManager, AccountService.
•
인터페이스 이름: PascalCase를 사용하여 작성하며 명사 혹은 동작을 나타내는 경우 형용사를 사용합니다.
◦
예시: Runnable, Serializable.
// 패키지 이름은 소문자를 사용합니다. 단어별 구문을 위해 언더스코어(_)나 대문자를 포함하지 않습니다
// 좋은 예
public class ContentHandler
public class ContentHandlerTest
public interface ContentHandlerFactory
public interface Closable
// 나쁜 예
public class contentHandler
public interface contenthandlerfactory
Java
복사
1.3 변수 & 메소드
•
변수: camelCase 형식을 사용하며, 소문자로 시작하고 명확하고 구체적인 이름을 사용합니다. 축약어는 피합니다.
// 좋은 예
private int userAge;
// 나쁜 예
private int a;
Java
복사
•
메소드 이름: camelCase 형식을 사용하며, 보통 동사로 시작하여 메소드가 수행하는 동작을 명확히 나타냅니다.
◦
예시: calculateTotalAmount(), fetchCustomerDetails()
// 좋은 예
동사사용 -> validateNumber()
동사 + 전치사 + 명사 -> mapToInt(), moveToDirectory(), copyToFile(), submitToExecutor()
전환메서드의 전치사 -> toString(), toModel();, fromTable(); of(),
Builder 패턴 적용한 클래스의 메서드의 전치사 -> User.build().withUserId(id);
// 보통 method 이름을 지을 때 대칭되는 기능을 만드는 경우가 많습니다.
add / remove
append / prepend
start / stop
begin / end
first / last
top / last
// 진위(참,거짓)를 나타내는 경우
isXXX / hasXXX / canXXX / exists / contains / equals
isNull(), isNumber(), hasConnection(), canSave() 등
public boolean hasConnetion() {
return true;
}
Java
복사
1.4 상수
•
상수는 대문자와 언더바를 사용해 작성하며, 모든 단어는 명확하게 분리해야 합니다.
◦
예시: MAX_RETRIES, DEFAULT_TIMEOUT.
// 좋은 예
public final int UNLIMITED = -1;
public final String HTTP_EXPRESSION = “POST”;
// 나쁜 예
public final int unlimited = -1;
public final String http_expression = “POST”;
Java
복사
1.5 임시 변수
•
임시로 사용하는 변수명은 최대한 full-text를 사용하고 중복되지 않도록 작성합니다.
// 좋은 예
InputParser inputParser = new InputParser();
OutputParser outputParser = new OutputParser();
ParserHandler parserHandler = new ParserHandler();
// 나쁜 예
InputParser p = new InputParser();
OutputParser parser = new OutputParser();
- 여러 종류의 parser가 동일한 {} 안에 있을 시 가독성이 떨어집니다.
ParserHandler ph = new ParserHandler();
- 약어를 사용할 시 정의되어 있거나 유추할 수 있는 약어로 작성해야 합니다.
Java
복사
2. 선언
클래스, 필드, 메소드, 변수, 전처리(import) 등도 규칙에 맞게 사용합니다.
2.1 전처리(import)
•
import는 와일드카드(*) 없이 사용하는 클래스 단위까지 작성합니다. 그렇지 않으면 해당 클래스가 참조될 때마다 import 되어있는 모든 자원을 할당받아 사용하기 때문입니다.
•
static import에서는 와일드카드를 허용합니다.
// 좋은 예
import java.util.List;
import java.util.ArrayList;
import static org.junit.jupiter.api.Assertions.*;
// 나쁜 예
import java.util.*;
// 위와 같은 경우를 방지하려면 intellij-settings에서 아래 붉은 박스 안에 숫자를 증가시킵니다.
Java
복사
2.1 제한자 우선순위
•
제한자의 경우 아래와 같은 우선순위를 가지고 작성합니다.
1 | 2 | 3 | 4 | 5 |
public/protected/private | abstract | static | final | synchronized |
// 좋은 예
public static final String CONFIG = "상수";
protected abstract String abstractMethod() {};
// 나쁜 예
public final static String CONFIG = "상수";
Java
복사
2.2 Annotation 선언
•
클래스, 인터페이스, 메서드, 생성자에 붙는 애너테이션은 선언 후 새줄을 사용합니다.
// 좋은 예
@Stable
private final byte[] value;
// 나쁜 예
@Serial private static final long serialVersionUID = -6849794470754667710L;
Java
복사
2.3 라인당 선언 수
•
한 줄에 여러 문장을 쓰지 않는다.
// 좋은 예
private final byte coder;
private int hash;
private int size;
// 나쁜 예
private final byte coder; private int hash;
int length, size;
Java
복사
2.3 배열 대괄호 선언
•
한 줄에 여러 문장을 쓰지 않는다.
// 좋은 예
String[] names;
// 나쁜 예
String names[];
Java
복사
2.4 Long 접미사 선언
•
소문자 사용 시 1과 구분하기 어려워 가독성이 떨어집니다.
// 좋은 예
long base = 54423234211L;
// 나쁜 예
long base = 54423234211l;
Java
복사
3. 들여쓰기
들여쓰기는 각 코드의 계층을 구분하는 것으로 일관되게 작성합니다.
•
계층을 나눌 때에는 tab(4space)로 구분합니다.
•
클래스, 메서드, 제어문 등의 코드 블럭()이 생길 때마다 1단계를 더 들여쓴다.
// 좋은 예
public static Path of(URI uri) {
String scheme = uri.getScheme();
if (scheme == null) {
throw new IllegalArgumentException("Missing scheme");
}
if (scheme.equalsIgnoreCase("file")) {
return FileSystems.getDefault().provider().getPath(uri);
}
for (FileSystemProvider provider: FileSystemProvider.installedProviders()) {
if (provider.getScheme().equalsIgnoreCase(scheme)) {
return provider.getPath(uri);
}
}
throw new FileSystemNotFoundException("Provider \"" + scheme + "\" not installed");
}
// 나쁜 예
public static Path of(URI uri){
String scheme =uri.getScheme();
if(scheme == null){
throw new IllegalArgumentException("Missing scheme");
}
if (scheme.equalsIgnoreCase("file")){
return FileSystems.getDefault().provider().getPath(uri);
}
for(FileSystemProvider provider: FileSystemProvider.installedProviders()){
if (provider.getScheme().equalsIgnoreCase(scheme)){
return provider.getPath(uri);
}
}
throw new FileSystemNotFoundException("Provider \"" + scheme + "\" not installed");
}
Java
복사
3.1 중괄호
•
줄의 마지막에 {로 쓰고 블럭을 닫았을 때 새줄로 }를 씁니다.
// 좋은 예
public static WritableByteChannel newChannel(OutputStream out) {
Objects.requireNonNull(out, "out");
if (out.getClass() == FileOutputStream.class) {
return ((FileOutputStream) out).getChannel();
}
return new WritableByteChannelImpl(out);
}
// 나쁜 예
public final class Channels {
public static WritableByteChannel newChannel(OutputStream out)
{
Objects.requireNonNull(out, "out");
if (out.getClass() == FileOutputStream.class)
{
return ((FileOutputStream) out).getChannel();
}
return new WritableByteChannelImpl(out);
}
}
Java
복사
•
{}은 생략 가능한 경우에도 작성해줍니다.
•
해당 라인 이후 코드를 작성한 개발자가 있을 때 오류가 발생할 수도 있습니다.
// 좋은 예
if (condition) {
System.out.println("hi");
}
doSomething();
// 나쁜 예
if (condition)
System.out.println("hi");
doSomething(); <- 실행될 수 있음
Java
복사
•
if, for, try 등 문법에 {}는 같은 줄에 작성합니다.
// 좋은 예
if (line.startWith(WARNING_PREFIX)) {
return LogPattern.WARN;
} else if (line.startWith(DANGER_PREFIX)) {
return LogPattern.NORMAL;
} else {
return LogPattern.NORMAL;
}
// 나쁜 예
if (line.startWith(WARNING_PREFIX)) {
return LogPattern.WARN;
}
else if (line.startWith(DANGER_PREFIX)) {
return LogPattern.DANGER;
}
else {
return LogPattern.NORMAL;
}
// 좋은 예
try {
writeLog();
} catch (IOException ioe) {
reportFailure(ioe);
} finally {
writeFooter();
}
// 나쁜 예
try {
writeLog();
}
catch (IOException ioe) {
reportFailure(ioe);
}
finally {
writeFooter();
}
Java
복사
3. 줄바꿈
•
줄바꿈은 작성한 명령어, 구문이 길어질 때 가독성을 위하여 내려씁니다.
•
주의) 무부별한 줄바꿈은 가독성을 헤칠 수 있습니다. 꼭!! 규칙성과 통일성을 가지고 작성해주세요.
•
extends , implements ,throws 선언 후
•
시작 소괄호(() 선언 후
•
콤마(,) 후
•
. 전
•
연산자 전
◦
+, , , /, %
◦
==, !=, >=, >,⇐, <, &&, ||
◦
&, |, ^, >>>, >>, <<, ?
◦
instanceof
// 예
public class longImplemnts implements
AutoClosable, Runnable, Serializable {
public MyExampleClass()
throws IOException, IllegalArgumentException {
// 소괄호 이후 줄바꿈
performCalculation(
10,
20,
"Result");
// 콤마(,) 이후 줄바꿈
List<String> items = Arrays.asList(
"Apple",
"Banana",
"Cherry"
);
// 연산자 앞에서 줄바꿈
int result = 5
+ 10
- 2
* 3
/ 4;
boolean isValid = (result > 10)
&& (result != 0)
|| (result < 20);
// 메소드 체이닝에서 . 전 줄바꿈
String formattedString = new StringBuilder()
.append("The result is: ")
.append(result)
.toString();
}
}
Java
복사
4. 빈줄
•
메소드 사이의 에는 빈줄로 구분합니다.
// 좋은 예
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
// 나쁜 예
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
Java
복사
4. 공백
•
빈줄을 포함하여 모든 줄은 탭이나 공백을 포함하지 않습니다.
•
[] 대괄호 작성 시 공백을 삽입합니다.
// 좋은 예
String[] spells = new String[] {"ㄱ", "ㄴ", "ㄷ"};
// 나쁜 예
String[]spells = new String[]{"ㄱ", "ㄴ", "ㄷ"};
Java
복사
•
{} 중괄호, () 소괄호 작성 시 공백을 삽입합니다.
// 좋은 예
public void print(String line) {
...
}
// 나쁜 예
public void print(String line){
...
}
// 좋은 예
public void example() {
// if 문 - 소괄호 앞에 공백
if (condition) {
System.out.println("Condition is true");
}
// for 문 - 소괄호 앞에 공백
for (int i = 0; i < 10; i++) {
System.out.println("Iteration: " + i);
}
// while 문 - 소괄호 앞에 공백
while (condition) {
System.out.println("Still true");
}
// catch 문 - 소괄호 앞에 공백
try {
riskyOperation();
} catch (Exception e) {
System.out.println("Exception caught");
}
// synchronized 블록 - 소괄호 앞에 공백
synchronized (this) {
System.out.println("Synchronized block");
}
// switch 문 - 소괄호 앞에 공백
switch (value) {
case 1:
System.out.println("Value is 1");
break;
case 2:
System.out.println("Value is 2");
break;
default:
System.out.println("Other value");
}
}
// 나쁜 예
public void example() {
if(condition){
System.out.println("Condition is true");
}
for(int i=0;i<10;i++){
System.out.println("Iteration: " + i);
}
while(condition){
System.out.println("Still true");
}
try{
riskyOperation();
}catch (Exception e){
System.out.println("Exception caught");
}
synchronized (this){
System.out.println("Synchronized block");
}
// switch 문 - 소괄호 앞에 공백
switch(value){
case 1:
System.out.println("Value is 1");
break;
case 2:
System.out.println("Value is 2");
break;
default:
System.out.println("Other value");
}
}
// 좋은 예
String message = (String) line;
boolean flag = (condition1 && condition2)
// 나쁜 예
String message = ( String ) rawLine;
boolean flag = ( condition1&&condition2 )
Java
복사
•
이항/삼항 연상자 시 공백
// 좋은 예
int finalScore += weight * rawScore - absentCount;
boolean flag = (isNew) ? "신규" : "기존";
// 나쁜 예
int finalScore += weight*rawScore-absentCount;
boolean flag = (isNew)?"신규":"기존";
Java
복사
5. 주석
주석은 코드 관리와 가독성면에서 꼭 필요한 기능입니다.
하지만 불필요한 주석이 많아 진다면 오히려 코드가 더러워지거나 불필요한 오해를 일으킬 수 있습니다.
5.1 주석 사용
•
단일 줄 주석: //를 사용하여 해당 코드에 대한 간단한 설명을 작성합니다.
// 사용자 로그인 상태 확인
if (user.isLoggedIn()) {
// 처리 로직
}
Java
복사
•
블록 주석: /* */를 사용하여 코드 블록 전체에 대한 설명을 작성합니다.
/**
* 주어진 채널에서 바이트를 디코딩하여 리더(reader)를 생성합니다.
*
* 생성된 스트림에는 최소 {@code minBufferCap} 바이트의 내부 입력 버퍼가 포함됩니다.
* 스트림의 {@code read} 메서드는 필요에 따라 기본 채널에서 바이트를 읽어 버퍼를 채웁니다.
* 채널이 바이트를 읽을 때 논블로킹(non-blocking) 모드에 있으면 {@link IllegalBlockingModeException}
* 예외가 발생합니다. 생성된 스트림은 그 외에는 버퍼링되지 않으며, {@link Reader#mark mark} 또는
* {@link Reader#reset reset} 메서드를 지원하지 않습니다. 스트림을 닫으면 채널도 함께 닫힙니다
*
* @param ch
* 바이트를 읽을 채널
*
* @param dec
* 사용할 문자셋 디코더
*
* @param minBufferCap
* 내부 바이트 버퍼의 최소 용량,
* 또는 구현에 따라 기본 용량을 사용하려면 {@code -1}
*
* @return 새로운 리더(reader)
* @throw IOException
*/
public static Reader newReader(ReadableByteChannel ch,
CharsetDecoder dec,
int minBufferCap) throws IOException {
Java
복사
•
주석은 꼭!! 정보를 제공하거나, 의도를 설명하거나, 결과를 경고하거나, TODO를 작성하거나, API를 작성할 때 쓰시기 바랍니다.
// 꼭 주석을 달아야만 코드를 이해할 수 있어야할까요?
// 직원에게 복지 혜택을 받을 자격이 있는지 검사한다.
if ((employee.flag && HOURLY_FLAG) && (employee.age > 65))
if (employee.isEligibleForFullBenefits())
Java
복사
5.2 의도 설명
왜 이 코드를 작성했는지, 어떤 의도를 가지고 있는지 설명합니다. 다른 개발자가 이 코드를 이해하는 데 도움을 줍니다.
java
코드 복사
// 데이터베이스가 비동기적으로 업데이트되었는지를 확인하고 최신 상태를 가져오기 위함
if (isDatabaseUpdated()) {
refreshData();
}
Java
복사
5.3 결과 경고
특정 코드를 사용할 때 발생할 수 있는 문제나 부작용에 대해 경고합니다.
java
코드 복사
// WARNING: 이 메서드는 null 값을 반환할 수 있으므로 null 체크가 필요
public String getUserName(int userId) {
// ...
return null; // 사용자가 존재하지 않을 때 null을 반환합니다.
}
Java
복사
5.4 TODO 작성
아직 구현하지 않았거나 나중에 개선할 사항에 대한 주석을 남깁니다.
java
코드 복사
// TODO: 이 메서드에 예외 처리를 추가
public void processOrder(Order order) {
// 주문 처리 로직
}
Java
복사
6. 예외처리
6.1 예외를 사용하라
•
오류 처리는 반환 값을 확인하는 방식보다는 예외를 사용하는 것이 더 명확하고 유지보수하기 좋습니다. 반환 값을 사용하면 오류 발생 여부를 확인하는 코드가 곳곳에 흩어져 코드의 흐름을 방해합니다. 예외는 오류가 발생했을 때 흐름을 명확하게 중단하고 처리할 수 있게 도와줍니다.
// 좋은 예
public int calculateDiscount(Customer customer) {
if (customer == null) {
throw new IllegalArgumentException("Customer cannot be null");
}
// 계산 로직
return discount;
}
// 나쁜 예
public int calculateDiscount(Customer customer) {
if (customer == null) {
return -1; // 오류를 반환 값으로 처리
}
// 계산 로직
return discount;
}
Java
복사
6.2 예외를 무시하지 마세요
•
예외가 발생했을 때, 예외를 절대 빈 catch 블록으로 처리하거나 무시하지 마세요. 예외가 발생한 이유를 명확하게 이해하고 적절하게 처리해야 합니다. 그렇지 않으면 디버깅이 어렵고 버그가 발생할 가능성이 커집니다.
// 좋은 예
try {
// 위험한 작업
} catch (Exception e) {
// 예외를 기록하고 다시 던지거나 적절히 처리
logger.error("Exception occurred", e);
throw e;
}
// 나쁜 예
try {
// 위험한 작업
} catch (Exception e) {
// 아무것도 하지 않음
}
Java
복사
6.3 복구할 수 있는 예외와 복구할 수 없는 예외를 구분하세요
•
복구할 수 있는 예외는 사용자에게 알려주거나 다른 방법으로 시도하는 등의 방법으로 처리해야 합니다. 반면에 복구할 수 없는 예외는 바로 로그를 기록하고 다시 예외를 던지거나 프로그램을 종료해야 합니다.
try {
// 파일을 읽음
} catch (FileNotFoundException e) {
// 복구할 수 있는 예외: 파일을 다시 요청하거나 기본 파일을 사용
logger.error("File not found, please check the file path.");
} catch (IOException e) {
// 복구할 수 없는 예외: 로그를 기록하고 예외를 다시 던짐
logger.error("IOException occurred while reading the file", e);
throw new RuntimeException(e);
}
Java
복사
6.4 호출자에게 문제를 알리기 위한 예외 던지기
•
예외는 프로그램의 정상적인 흐름을 벗어나는 문제를 처리하는 메커니즘입니다. 호출자에게 문제가 발생했다는 사실을 알려주고, 필요하다면 이를 처리할 수 있는 기회를 줍니다. 예외를 던질 때는 의미 있는 메시지와 함께 던져야 합니다.
// 메소드를 호출한 주체는 메소드가 전달한 예외처리할 의무가 있습니다.
try {
file.openFile(path);
} catch(FileNotFoundExcetion e) {
// 예외 처리
...
}
public void openFile(String filePath) throws FileNotFoundException {
File file = new File(filePath);
if (!file.exists()) {
throw new FileNotFoundException("File not found at path: " + filePath);
}
}
Java
복사
6.5 특정 예외를 처리하라
•
너무 포괄적인 catch(Exception e) 구문보다는, 가능한 한 구체적인 예외를 처리하는 것이 좋습니다. 구체적인 예외를 처리하면 예상치 못한 오류를 방지하고, 디버깅이 쉬워집니다.
// 좋은 예
try {
// 작업
} catch (FileNotFoundException e) {
// 파일이 없을 때의 처리
} catch (IOException e) {
// 입출력 오류 처리
}
// 나쁜 예
try {
// 작업
} catch (Exception e) {
// 오류 처리
}
Java
복사
6.6 null을 전달하지도 반환하지 마세요
•
null을 반환하면 호출하는 측에서 반드시 null 체크를 해야 하며, 이를 잊어버리면 * * NullPointerException(NPE)이 발생할 위험이 있습니다. 코드가 복잡해지고 오류 발생 가능성이 커집니다.
•
null 대신에 의미 있는 값이나 빈 객체(Empty Object), Optional을 반환하는 것이 좋습니다.
// 좋은 예
public List<Item> getItems(Customer customer) {
if (customer == null) {
return Collections.emptyList(); // 빈 리스트 반환
}
return customer.getItems();
}
// 호출하는 쪽에서 null 체크 불필요
for (Item item : getItems(customer)) {
processItem(item);
}
// 좋은 예
public Optional<Item> findItemById(int id) {
Item item = findItemInDatabase(id);
return Optional.ofNullable(item); // null 대신 Optional 반환
}
// 호출하는 쪽에서 더 명확하게 null 여부를 처리
findItemById(123).ifPresent(item -> processItem(item));
// 나쁜 예
public List<Item> getItems(Customer customer) {
if (customer == null) {
return null; // null 반환
}
return customer.getItems();
}
// 호출하는 쪽에서는 null 체크를 강제로 해야 함
List<Item> items = getItems(customer);
if (items != null) {
for (Item item : items) {
processItem(item);
}
}
Java
복사
•
메서드에 null을 인자로 전달하면 해당 메서드 안에서 null 체크를 하거나, null 처리에 대한 복잡한 논리를 추가해야 합니다. null을 인자로 받는 메서드가 많아지면 코드가 복잡해지고, 오류 발생 가능성이 커집니다.
// 좋은 예
public void processOrder(Order order) {
// Null이 아닌, 항상 유효한 객체를 전달받는다고 가정
// 따라서 별도의 null 체크가 필요하지 않음
}
// 호출할 때 null 대신 빈 객체를 전달
Order emptyOrder = new Order(); // 빈 객체 생성
processOrder(emptyOrder);
// 나쁜 예
public void processOrder(Order order) {
if (order != null) {
// 처리 로직
}
}
// 호출 시 null을 전달하는 나쁜 습관
processOrder(null);
Java
복사






