회원가입을 하면 원하는 문장을
저장할 수 있어요!
다음
AWS 이용 중이라면 최대 700만 원 지원받으세요
클린 코드(Clean Code)는 소프트웨어 개발에서 사용되는 개념으로, 읽기 쉽고 이해하기 쉬운 코드를 작성하는 것을 강조합니다. 클린 코드는 프로그램의 동작을 보장하는 것뿐만 아니라, 코드 자체가 가독성이 뛰어나고 유지 보수가 쉽도록 작성되어야 한다는 원칙에 기반합니다.
이는 개발팀이 있는 소프트웨어 개발사뿐만 아니라, 오픈 소스 혹은 다양한 종류의 소프트웨어 개발자 커뮤니티에서도 중요하게 강조되는 원칙 중 하나이며, 좋은 소프트웨어 개발 실천의 일환으로 여겨집니다. 그런데 현실적으로 실천하려면 어떻게 해야 할까요? 이번 글에서는 클린 코드가 중요한 이유와 이를 실천하는 방법에 대해 살펴보겠습니다.
회원가입을 하면 원하는 문장을
저장할 수 있어요!
다음
회원가입을 하면
성장에 도움이 되는 콘텐츠를
스크랩할 수 있어요!
확인
클린 코드(Clean Code)는 소프트웨어 개발에서 사용되는 개념으로, 읽기 쉽고 이해하기 쉬운 코드를 작성하는 것을 강조합니다. 클린 코드는 프로그램의 동작을 보장하는 것뿐만 아니라, 코드 자체가 가독성이 뛰어나고 유지 보수가 쉽도록 작성되어야 한다는 원칙에 기반합니다.
이는 개발팀이 있는 소프트웨어 개발사뿐만 아니라, 오픈 소스 혹은 다양한 종류의 소프트웨어 개발자 커뮤니티에서도 중요하게 강조되는 원칙 중 하나이며, 좋은 소프트웨어 개발 실천의 일환으로 여겨집니다. 그런데 현실적으로 실천하려면 어떻게 해야 할까요? 이번 글에서는 클린 코드가 중요한 이유와 이를 실천하는 방법에 대해 살펴보겠습니다.
우선 클린 코드가 개발팀에 중요한 이유는 소프트웨어 개발 과정에서 코드의 품질로 인해 발생할 수 있는 다양한 문제를 예방하고, 품질 높은 소프트웨어를 만들기 위해서입니다.
물론 어떤 개발자든 지저분한 코드로 프로젝트를 계속 진행하고 싶진 않을 겁니다. 그러나 때때로 마감 기한 압박으로 개발자가 클린 코드 작성을 피하는 경우도 있죠. 더 빠른 개발 진도를 위해 서두르지만, 결국 나중에 동일한 기능을 수행하는 코드로 다시 돌아가 버그를 수정하거나, 새로운 기능을 추가하는 등 유지 보수 작업이 필요합니다. 이런 경우 코드를 수정하느라 훨씬 더 많은 비용이 발생합니다.
간결하고 명확한 클린 코드는 버그를 찾기 쉽게 만들어 줍니다. 반면 복잡하고 난해한 코드는 오류가 발생하기 쉽고, 발견한 후에도 수정이 어려울 수 있습니다. 또한 가독성이 높고 유지 보수가 용이한 코드는 개발 속도를 향상시킵니다. 코드를 이해하고 수정하는 데 걸리는 시간이 줄기 때문에 새로운 기능을 빠르게 구현하고 배포할 수 있습니다.
이러한 이유로 클린 코드는 비단 개발자 한 사람뿐만 아니라, 팀 전체와 프로젝트의 성공에 기여하는 중요한 가치를 가지고 있습니다.
클린 코드의 주요 원칙은 다음과 같습니다.
변수와 함수는 그 사용 문맥에 맞게 명확하게 명명되어야 합니다. 이를 통해 코드의 이해가 쉬워지고 버그 발생 가능성이 줄어듭니다.
코드는 다른 사람이나 나중에 자신이 읽기 쉽게 작성되어야 합니다. 의미 있는 변수명, 함수명, 클래스명 등을 사용하여 코드의 의도를 명확하게 전달해야 합니다.
public class Calculator {
public static void main(String[] args) {
double a = 10, b = 5;
double result = add(a, b);
System.out.println("Sum is: " + result);
}
public static double add(double num1, double num2) {
return num1 + num2;
}
}
<예제 코드 1-1.>
앞에서 언급한 대로 변수명을 의미 있는 변수와 함수로 더 명확하게 변경할 필요가 있습니다. 예를 들면, a, b 대신에 operand1, operand2로 변경하여 변수의 역할을 명확히 하는 것이죠. operand는 수식에서 연산이 수행되는 값 또는 피연산자를 나타내는 일반적인 용어입니다. 예를 들어, 덧셈 연산 a + b에서 a와 b는 두 피연산자 또는 operand입니다. 예제 코드에서는 연산에 참여하는 값이라는 의미를 갖고 있습니다. 덕분에 각 변수가 어떤 역할을 하는지 명확하게 이해할 수 있습니다.
메서드 명도 마찬가지입니다. 예제 코드 1-2에선 add 대신에 addNumbers로 변경하여 메서드의 역할을 명확히 합니다. 결과적으로 ‘의미 있는 변수와 함수 사용’과 더불어 코드의 가독성이 향상되었습니다.
public class Calculator {
public static void main(String[] args) {
double operand1 = 10;
double operand2 = 5;
double sum = addNumbers(operand1, operand2);
System.out.println("Sum is: " + sum);
}
public static double addNumbers(double num1, double num2) {
return num1 + num2;
}
}
<예제 코드 1-2. 가독성을 추가한 코드>
코드의 의도나 중요한 부분에 주석을 추가하여 다른 개발자가 코드를 이해하는 데 도움을 줍니다. 그러나 주석이 필요한 경우에 주석을 추가하는 것보다는 코드 자체로 의도를 명확히 드러내는 것이 좋습니다.
불필요한 코드는 피하고, 간결한 코드를 유지해야 합니다. 간결한 코드는 버그를 줄이고 유지 보수를 용이하게 만듭니다. 아래 예제 코드 2-1은 사용자에게 두 개의 변수를 입력받아 처리하는 과정을 보여줍니다.
import java.util.Scanner;
public class Calculator {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("Enter the first number: ");
double operand1 = scanner.nextDouble();
System.out.print("Enter the second number: ");
double operand2 = scanner.nextDouble();
double sum = operand1 + operand2;
System.out.println("Sum is: " + sum);
scanner.close();
}
<예제 코드 2-1.>
먼저 동일한 기능을 수행하는 부분이 있는데, 바로 사용자 입력을 두 번 받는 부분입니다. 예제 코드 3-2에서는 이것을 getUserInput 메서드로 추출하여 중복을 제거했습니다. 또한 Scanner를 getUserInput 메서드 내부에서 생성하여 메서드 실행 완료 후 자동으로 close되도록 변경했습니다.
결과적으로 메시지 출력과 입력을 한 줄로 처리하여 코드를 그리 어렵지 않은 방식으로 줄였습니다. 이러한 변경으로 코드의 가독성이 향상되고, 중복이 제거되어 더 간결하고 가독성이 향상된 코드가 되었습니다. 또한 주석을 사용하여 코드의 목적과 각 부분의 역할을 간략하게 설명했습니다. 이는 코드의 의도를 설명하여 다른 개발자가 코드를 이해하는 데 도움이 됩니다.
import java.util.Scanner;
public class Calculator {
public static void main(String[] args) {
// 사용자로부터 두 개의 숫자를 입력받아 덧셈 결과를 출력하는 프로그램
double operand1 = getUserInput("Enter the first number: ");
double operand2 = getUserInput("Enter the second number: ");
// 입력받은 두 숫자를 더함
double sum = operand1 + operand2;
// 결과 출력
System.out.println("Sum is: " + sum);
}
// 사용자로부터 입력을 받는 메서드
private static double getUserInput(String message) {
System.out.print(message);
return new Scanner(System.in).nextDouble();
}
}
<예제 코드 2-2. 필요한 주석의 추가와 코드 간결화하기>
코드를 작은 모듈로 나누어 각 모듈이 특정 기능이나 역할을 수행하도록 해야 합니다. 이는 코드의 재사용성을 높이고 유지 보수를 쉽게 만듭니다.
아래 예제 코드는 사용자에게 두 개의 변수를 입력받고 이를 사칙연산 하는 경우를 구현한 예제입니다.
import java.util.Scanner;
public class Calculator {
public static void main(String[] args) {
//두 개의 숫자를 입력받음
double operand1 = getUserInput("Enter the first number: ");
double operand2 = getUserInput("Enter the second number: ");
//사칙연산 구현
double sum = operand1 + operand2;
double difference = operand1 - operand2;
double product = operand1 * operand2;
double quotient = operand1 / operand2;
//결과 출력
System.out.println("Sum is: " + sum);
System.out.println("Difference is: " + difference);
System.out.println("Product is: " + product);
System.out.println("Quotient is: " + quotient);
}
// 사용자로부터 입력을 받음
private static double getUserInput(String message) {
System.out.print(message);
return new Scanner(System.in).nextDouble();
}
}
<예제 코드 3-1.>
모듈화를 적용하기 위해 분리가 가능한 기능들을 살펴봅니다. 예제 코드 3-2에서는 일단 더 작은 기능을 수행하는 메서드들로 코드를 모듈화가 필요한데, 분리된 기능을 수행하는 add, subtract, multiply, divide 메서드들은 각각 덧셈, 뺄셈, 곱셈, 나눗셈을 수행합니다.
displayResult 메서드는 결과를 출력하는 역할을 수행합니다. 코드의 가독성이 향상되고, 각각의 기능이 명확하게 분리되어 재사용성이 높아졌습니다. 나눗셈에서 0으로 나누는 경우를 처리하도록 하여 예외 상황에 대한 처리를 추가했습니다.
import java.util.Scanner;
public class Calculator {
public static void main(String[] args) {
//두 개의 숫자를 입력받음
double operand1 = getUserInput("Enter the first number: ");
double operand2 = getUserInput("Enter the second number: ");
//사칙연산 구현
double sum = add(operand1, operand2);
double difference = subtract(operand1, operand2);
double product = multiply(operand1, operand2);
double quotient = divide(operand1, operand2);
//결과 출력 메서드 호출
displayResult("Sum is: ", sum);
displayResult("Difference is: ", difference);
displayResult("Product is: ", product);
displayResult("Quotient is: ", quotient);
}
private static double add(double num1, double num2) {
return num1 + num2;
}
private static double subtract(double num1, double num2) {
return num1 - num2;
}
private static double multiply(double num1, double num2) {
return num1 * num2;
}
private static double divide(double num1, double num2) {
if (num2 != 0) {
return num1 / num2;
} else {
System.out.println("Cannot divide by zero.");
return Double.NaN;
}
}
//결과를 출력
private static void displayResult(String message, double result) {
System.out.println(message + result);
}
//사용자로부터 입력을 받음
private static double getUserInput(String message) {
System.out.print(message);
return new Scanner(System.in).nextDouble();
}
}
<예제 코드 3-2. 첫 번째 모듈화를 수행한 코드>
모듈화를 지나치게 적용하면 코드의 길이가 증가할 수 있으며, 이는 가독성과 간결성을 해칠 수 있습니다. 항상 적절한 수준에서 모듈화를 적용해야 합니다. 작은 규모의 프로그램이나 간단한 기능의 경우에는 모듈화를 과도하게 적용하지 않고, 코드를 간결하게 유지하는 것이 좋습니다.
위 예제에서 사칙연산을 간단하게 수행하는 데 굳이 코드를 저렇게 늘릴 필요가 있을까라고 판단 하고 코드의 최적화를 수행해 봅니다.
import java.util.Scanner;
public class SimpleCalculator {
public static void main(String[] args) {
double operand1 = getUserInput("Enter the first number: ");
double operand2 = getUserInput("Enter the second number: ");
// 덧셈, 뺄셈, 곱셈, 나눗셈 결과 계산 및 출력
calculateAndDisplay(operand1, operand2, "Sum", (a, b) -> a + b);
calculateAndDisplay(operand1, operand2, "Difference", (a, b) -> a - b);
calculateAndDisplay(operand1, operand2, "Product", (a, b) -> a * b);
calculateAndDisplay(operand1, operand2, "Quotient", (a, b) -> (b != 0) ? a / b : Double.NaN);
}
private static double getUserInput(String message) {
System.out.print(message);
return new Scanner(System.in).nextDouble();
}
private static void calculateAndDisplay(double num1, double num2, String operation, DoubleOperation op) {
double result = op.apply(num1, num2);
System.out.println(operation + " is: " + result);
}
@FunctionalInterface
private interface DoubleOperation {
double apply(double a, double b);
}
}
<예제 코드 3-3. 두 번째 모듈화를 수행한 코드>
예제 코드 3-3에서는 람다 표현식을 활용하여 간단한 연산을 처리하는 함수형 인터페이스 DoubleOperation을 도입했고, calculateAndDisplay 메서드에서 각 연산을 처리하고 결과를 출력합니다. 결과적으로 하나의 메서드로 여러 연산을 처리하면서도 코드를 간결하게 유지하여 불필요한 코드의 라인을 줄이고 기능을 최적화했습니다.
클린 코드는 테스트가 쉽게 가능한 구조여야 합니다. 테스트 케이스를 작성하고 유지 보수할 때 테스트가 도움이 되도록 코드를 작성하는 것이 중요합니다. 테스트 가능성을 고려한 코드는 주로 모듈화, 의존성 주입, 인터페이스 활용 등을 통해 적용할 수 있습니다.
위에서 소개한 원칙은 클린 코드를 수행하는 알려진 많은 방법 중 가장 대표적인 예제였습니다. 더 자세한 내용은 Robert Martin의 <Clean Code>에 언급된 원칙 요약을 참고하세요.
이러한 클린 코드 원칙을 현실적으로 적용하는 것은 사실 쉽지 않은 과제입니다. 총 15명으로 이루어진 제가 속한 개발팀에서는 다음과 같은 이유로 클린 코드를 지키기 어려웠습니다.
프로젝트 일정과 마감일은 지키는 것은 중요한 일입니다. 그래서 개발자들은 종종 기능 위주의 코드를 빠르게 작성해야 한다는 압박이 있고, 이때 클린 코드 작성은 부담이 될 수 있습니다. 이는 기술 부채(Technical Debt)의 형태로 나타나기도 하며, 프로젝트 초기 빠르게 개발하면서 점점 크기가 커집니다. 나중에는 정리하려던 코드를 더 이상 손을 대지 못하는 상태에서 계속 개발을 진행해야 하는 상태가 될 수도 있죠. 클린 코드 작성을 통해 기술 부채를 방지하려면 추가적인 시간과 노력이 필요합니다.
팀원 간에 코드 작성 스타일이나 클린 코드에 관한 이해 차이가 있을 수 있습니다. 특히 이것은 주니어 개발자 그룹과 시니어 개발자 그룹에서 두드러지게 나타나는데요. 팀 내에서 일관된 코드 스타일과 클린 코드 원칙을 적용하기 위해 꾸준한 논의와 교육이 필요합니다.
이미 존재하는 레거시 코드와 새로운 코드를 통합하는 것은 처음부터 모든 코드를 만드는 것보다 두 배 이상 어렵습니다. 특히 레거시 코드의 품질이 낮거나, 클린 코드 원칙과 맞지 않는 경우가 그러한데 이때 충분한 리팩토링이 필요합니다.
비즈니스 요구 사항의 변화는 코드 수정을 필요로 합니다. 이러한 변화 대응을 위해 코드를 변경할 때, 클린 코드를 유지하면서 기능을 변경하려면 더 큰 노력을 들여야 합니다.
이렇듯 클린 코드를 현실적으로 적용하는 것은 어려운 일이지만, 팀의 역량 향상과 소프트웨어의 장기적인 유지 보수성을 개선하기 위해 꼭 필요한 일입니다.
저의 경우, S사의 솔루션 개발 센터에서 다양한 웹과 윈도 기반 솔루션 개발팀을 담당했을 때, 제일 먼저 클린 코드를 우선적으로 적용할 프로젝트를 선별했습니다. 이는 모든 프로젝트의 모든 코드가 깨끗하고 아름다울 필요는 없다는 인식에서 시작한 것이죠.
그다음 각 개발 파트 리더와 개발자들에게 왜 클린 코드를 수행해야 하는지, 이해와 참여를 구하는 데 오랜 시간을 할애했습니다. 바쁜 프로젝트 일정 중에서도 팀에서 정한 클린 코드 규칙대로 개발하고, 동료 리뷰에서 이 지침을 잊지 않도록 팀의 프로세스를 명문화하고 실천했습니다.
시간이 흘러 스파게티 코드(소스 코드가 복잡하게 얽힌 모습을 비유)가 점점 사라지고, 빠른 이해와 유지 보수가 용이한 코드만 남았을 때, 비로소 클린 코드 내재화에 대한 충분한 효과와 보상을 느낄 수 있었습니다.
아직 클린 코드가 익숙하지 않다면, 초기 실천은 어렵겠지만 개발팀은 클린 코드 원칙을 잊지 않아야 합니다. 현실적인 제약을 고려해 이를 적용하며, 꾸준한 노력을 통해 코드 품질을 향상시키는 방향으로 나아갈 수 있길 바랍니다.
요즘IT의 모든 콘텐츠는 저작권법의 보호를 받는 바, 무단 전재와 복사, 배포 등을 금합니다.