Jul 10th Internship journal:
Today, I researched about the basic concepts of clean coding, a term that refers to the code format with high readability and efficiency. The team could potentially use this introductory information for designing the website we will create. For better understanding, I tried to read Robert C. Martin’s original book. However, with a lack of background knowledge, the later parts–like data structures, boundaries, and unit tests–were hard to grasp, and thus could not be included in the summary. I felt the vital need to do further research to effectively contribute to the project.
Jul 12th
Today I researched about static program analysis and looked at some example programs that help this.
클린 코드란?
클린 코드는 미국의 프로그래머 Robert C. Martin이 그의 책에서 사용한 용어로, 명확하고 간결하여 이해하기 쉬운 코드를 의미합니다. 이는 코드의 가독성, 유지 보수성, 효율성을 높여주기 때문에 소프트웨어 개발에서 매우 중요한 개념입니다.
왜 클린 코드가 필요한가요?
다른 사람들이 코드를 쉽게 이해하고 수정할 수 있기 때문에 팀워크가 원활하고 효과적으로 이루어집니다. 또한, 코드를 이해하기 쉬우면 버그를 훨씬 더 쉽게 찾고 수정할 수 있습니다. 따라서 장기적으로 봤을 때 많은 개발 비용을 절감할 수 있으며, 팀의 생산성을 크게 향상시킵니다.
클린코딩의 원칙
클린 코딩의 원칙들을 간단한 예시와 함께 몇 가지만 살펴 보겠습니다.
1.이름을 정확하게 지어라
변수, 함수,또는 클래스의 이름을 통해 왜 존재 이유와 역할을 정확히 알 수 있어야 합니다.
def c(a, b):
s = 0
for i in range(a, b + 1):
s += i
return s
x = 1
y = 10
result = c(x, y)
print(result)
def calculate_sum_of_range(start, end):
total_sum = 0
for number in range(start, end + 1):
total_sum += number
return total_sum
start_number = 1
end_number = 10
sum_result = calculate_sum_of_range(start_number, end_number)
print(sum_result)
위의 코드보다는 아래의 코드가 훨씬 이해하기 쉬운데, 이는 메서드와 변수들의 이름을 통해 그 역할과 존재 의의를 명확하게 정의해 주었기 때문이죠.
2.함수는 간단하게 하라
함수는 단 하나의 기능만 수행해야 해요. 길이가 20줄 이상을 넘어가지 않는 것을 권장합니다.
def process_data(data):
filtered_data = [item for item in data if item['value'] > 10]
sorted_data = sorted(filtered_data, key=lambda x: x['value'])
transformed_data = [{'id': item['id'], 'value': item['value'] * 2} for item in sorted_data]
for item in transformed_data:
print(f"ID: {item['id']}, Value: {item['value']}")
이 함수는 무려 4개의 기능을 수행하는데, 각각의 기능을 다른 함수 안에 넣어야 합니다.
def filter_data(data, threshold):
return [item for item in data if item['value'] > threshold]
def sort_data(data):
return sorted(data, key=lambda x: x['value'])
def transform_data(data):
return [{'id': item['id'], 'value': item['value'] * 2} for item in data]
def print_data(data):
for item in data:
print(f"ID: {item['id']}, Value: {item['value']}")
# Main function to process the data using the smaller functions
def process_data(data, threshold=10):
filtered_data = filter_data(data, threshold)
sorted_data = sort_data(filtered_data)
transformed_data = transform_data(sorted_data)
print_data(transformed_data)
3.코멘트를 남발하지 마라
코멘트는 저작권을 표기하거나, 이해하기 어려운 함수를 설명하거나, 의도를 밝히거나, 경고성 문구가 아니라면 쓰지 마세요.
// Returns an instance of the Responder being tested.
protected abstract Responder responderInstance();
// Don't run unless you
// have some time to kill.
public void _testWithReallyBigFile() {
writeLinesToFile(10000000);
response.setBody(testFile);
response.readyToSend(this);
String responseString = output.toString(); assertSubString("Content-Length: 1000000000", responseString); assertTrue(bytesSent > 1000000000);
}
위 코드에는 설명을 위한, 아래 코드에는 경고를 위한 코멘트가 달려 있습니다. 이런 코멘트들이 좋다고 할 수 있어요.
4.구조를 깔끔히 하라
책에서는 수직/수평 구조에 대해서 설명합니다. 세로로는 코드를 신문처럼 중요도에 따라서 위-아래로 정렬하고, 사용처에서 가장 가까운 곳에 변수를 정의해야 합니다. 또 가로로는 코드가 화면을 벗어나지 않게 하고, 짧든 길든 항상 들여쓰기를 해야 클린한 코드가 됩니다.
import java.util.regex.*;
public class BoldWidget extends ParentWidget {
public static final String REGEXP = “‘’’.+?’’’”;
private static final Pattern pattern = Pattern.compile(“‘’’(.+?)’’’”,
Pattern.MULTILINE + Pattern.DOTALL);
public BoldWidget(ParentWidget parent, String text) throws Exception {
super(parent);
Matcher match = pattern.matcher(text); match.find(); addChildWidgets(match.group(1));}
public String render() throws Exception { StringBuffer html = new StringBuffer(“<b>”); html.append(childHtml()).append(“</b>”); return html.toString();
} }
package fitnesse.wikitext.widgets;
import java.util.regex.*;
public class BoldWidget extends ParentWidget {
public static final String REGEXP = "'''.+?'''";
private static final Pattern pattern =
Pattern.compile("'''(.+?)'''",
Pattern.MULTILINE + Pattern.DOTALL
);
public BoldWidget(ParentWidget parent, String text) throws Exception {
super(parent);
Matcher match = pattern.matcher(text);
match.find();
addChildWidgets(match.group(1));
}
public String render() throws Exception {
StringBuffer html = new StringBuffer("<b>");
html.append(childHtml()).append("</b>");
return html.toString();
}
}
5. 디미터의 법칙을 준수하라
디미터의 법칙은 객체는 다른 객체의 내부 세부 사항에 의존하지 말고, 제공된 인터페이스를 통해서만 상호작용해야 한다는 법칙입니다.
class Engine {
public void start() {
System.out.println("Engine started");
}
}
class Car {
private Engine engine;
public Car() {
this.engine = new Engine();
}
public Engine getEngine() {
return engine;
}
}
class Driver {
public void startCar(Car car) {
car.getEngine().start();
}
}
public class Main {
public static void main(String[] args) {
Car car = new Car();
Driver driver = new Driver();
driver.startCar(car);
}
}
여기서는 driver가 car의 내부 구조 (engine)에 대해서 알고 있기 때문에 디미터의 법칙을 위반합니다.
class Engine {
public void start() {
System.out.println("Engine started");
}
}
class Car {
private Engine engine;
public Car() {
this.engine = new Engine();
}
public void startEngine() {
engine.start();
}
}
class Driver {
public void startCar(Car car) {
car.startEngine();
}
}
public class Main {
public static void main(String[] args) {
Car car = new Car();
Driver driver = new Driver();
driver.startCar(car); //
}
}
이 코드는 Driver가 car의 내부 기능을 몰라도 되기 때문에 클린 코드라고 할 수 있겠죠.
6.오류 코드를 예외로 대체하라
class FileReader {
public void readFile(String filePath) throws FileNotFoundException {
if (filePath == null) {
throw new FileNotFoundException("File path is null");
}
System.out.println("Reading file: " + filePath);
}
}
public class Main {
public static void main(String[] args) {
FileReader fileReader = new FileReader();
try {
fileReader.readFile(null);
System.out.println("File read successfully");
} catch (FileNotFoundException e) {
System.out.println("Error: " + e.getMessage());
}
}
}
위 코드는 오류가 생길 때 -1을 반환하거나 하지 않고, 즉시 FileNotFoundException을 발생시키기 때문에 더 깔끔합니다.
Boy Scout Rule
def calculate_total_price(items, tax_rate):
total = 0
for item in items:
total += item['price']
total += total * tax_rate
return total
def apply_discount(price, discount):
return price - (price * discount)
items = [{'name': 'apple', 'price': 0.5}, {'name': 'banana', 'price': 0.3}]
tax_rate = 0.07
discount = 0.1
total_price = calculate_total_price(items, tax_rate)
discounted_price = apply_discount(total_price, discount)
print(discounted_price)
def calculate_subtotal(items):
subtotal = 0
for item in items:
subtotal += item['price']
return subtotal
def calculate_total_price(subtotal, tax_rate):
return subtotal + (subtotal * tax_rate)
def apply_discount(price, discount):
return price - (price * discount)
items = [{'name': 'apple', 'price': 0.5}, {'name': 'banana', 'price': 0.3}]
tax_rate = 0.07
discount = 0.1
subtotal = calculate_subtotal(items)
total_price = calculate_total_price(subtotal, tax_rate)
discounted_price = apply_discount(total_price, discount)
print(discounted_price)
이에 관련하여 클린한 객체 지향 프로그래밍을 위해 매우 중요한 SOLID 원칙 또한 알아보겠습니다.
S (Single responsibility): 함수와 마찬가지로, 한 클래스가 하나의 역할만 해야 한다는 원칙입니다.
O (Open-closed): 코드가 확장에는 열려 있으되, 수정에는 닫혀 있어야 한다는 원칙입니다.
L (Liskov substitution): 기반 클래스가 할 수 있는 모든 일은 파생 클래스 또한 할 수 있어야 합니다.
이 원칙을 설명하기 위해 흔히 사용되는 예시인 직사각형과 정사각형입니다.
class Rectangle {
protected int width;
protected int height;
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public int getArea() {
return width * height;
}
}
class Square extends Rectangle {
@Override
public void setWidth(int width) {
this.width = width;
this.height = width;
}
@Override
public void setHeight(int height) {
this.width = height;
this.height = height;
}
}
public class Main {
public static void main(String[] args) {
Rectangle rectangle = new Rectangle();
rectangle.setWidth(5);
rectangle.setHeight(10);
System.out.println("Rectangle area: " + rectangle.getArea());
Rectangle square = new Square();
square.setWidth(5);
square.setHeight(10);
System.out.println("Square area: " + square.getArea());
}
}
이 코드는 LSP를 위반했기 때문에 정사각형의 면적이 잘못 계산됩니다. 이를 해결하려면 직사각형과 정사각형을 별도의 클래스로 만들고 같은 인터페이스를 써야 합니다.
interface Shape {
int getArea();
}
class Rectangle implements Shape {
protected int width;
protected int height;
public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
@Override
public int getArea() {
return width * height;
}
}
class Square implements Shape {
private int side;
public Square(int side) {
this.side = side;
}
public int getSide() {
return side;
}
@Override
public int getArea() {
return side * side;
}
}
public class Main {
public static void main(String[] args) {
Shape rectangle = new Rectangle(5, 10);
System.out.println("Rectangle area: " + rectangle.getArea());
Shape square = new Square(5);
System.out.println("Square area: " + square.getArea());
}
}
I (Interface segregation): 클라이언트가 사용하지 않는 인터페이스에 의존해서는 안 됩니다.
interface Worker {
void work();
}
interface Eater {
void eat();
}
class Human implements Worker, Eater {
@Override
public void work() {
System.out.println("Human is working");
}
@Override
public void eat() {
System.out.println("Human is eating");
}
}
class Robot implements Worker {
@Override
public void work() {
System.out.println("Robot is working");
}
}
여기서 work와 eater가 따로 정의됐기 때문에 Robot은 불필요한 eat 메서드를 구현할 필요가 없어집니다.
D (Dependency Inversion): 상위 모듈은 하위 모듈에 의존해서는 안 된다는 원칙입니다.
이외에도 디자인이나 시스템에 관련된 더 복잡한 원칙들이 존재합니다.
버그를 일으키지는 않지만 이러한 원칙들을 지키지 않아 유지 보수성이 떨어지는 코드를 code smell(냄새나는 코드)이라고 하며, 냄새를 감지하기 위해서 다양한 도구들이 존재합니다.
Top comments (0)