DEV Community

Dongdiri96
Dongdiri96

Posted on • Edited on

Clean Coding research

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)
Enter fullscreen mode Exit fullscreen mode
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)
Enter fullscreen mode Exit fullscreen mode

위의 코드보다는 아래의 코드가 훨씬 이해하기 쉬운데, 이는 메서드와 변수들의 이름을 통해 그 역할과 존재 의의를 명확하게 정의해 주었기 때문이죠.

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']}")
Enter fullscreen mode Exit fullscreen mode

이 함수는 무려 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)
Enter fullscreen mode Exit fullscreen mode

3.코멘트를 남발하지 마라
코멘트는 저작권을 표기하거나, 이해하기 어려운 함수를 설명하거나, 의도를 밝히거나, 경고성 문구가 아니라면 쓰지 마세요.

// Returns an instance of the Responder being tested.
protected abstract Responder responderInstance();
Enter fullscreen mode Exit fullscreen mode
// 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);
}
Enter fullscreen mode Exit fullscreen mode

위 코드에는 설명을 위한, 아래 코드에는 경고를 위한 코멘트가 달려 있습니다. 이런 코멘트들이 좋다고 할 수 있어요.

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();
} }
Enter fullscreen mode Exit fullscreen mode
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();
  } 
}
Enter fullscreen mode Exit fullscreen mode

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); 
    }
}
Enter fullscreen mode Exit fullscreen mode

여기서는 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); //
    }
}
Enter fullscreen mode Exit fullscreen mode

이 코드는 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());
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

위 코드는 오류가 생길 때 -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)
Enter fullscreen mode Exit fullscreen mode
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)
Enter fullscreen mode Exit fullscreen mode

이에 관련하여 클린한 객체 지향 프로그래밍을 위해 매우 중요한 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());
    }
}
Enter fullscreen mode Exit fullscreen mode

이 코드는 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());
    }
}
Enter fullscreen mode Exit fullscreen mode

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");
    }
}

Enter fullscreen mode Exit fullscreen mode

여기서 work와 eater가 따로 정의됐기 때문에 Robot은 불필요한 eat 메서드를 구현할 필요가 없어집니다.

D (Dependency Inversion): 상위 모듈은 하위 모듈에 의존해서는 안 된다는 원칙입니다.

이외에도 디자인이나 시스템에 관련된 더 복잡한 원칙들이 존재합니다.

버그를 일으키지는 않지만 이러한 원칙들을 지키지 않아 유지 보수성이 떨어지는 코드를 code smell(냄새나는 코드)이라고 하며, 냄새를 감지하기 위해서 다양한 도구들이 존재합니다.

Top comments (0)