Clean Coding research
Published: 2024-07-10 10:33:52

**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개의 기능을 수행하는데, 각각의 기능을 다른 함수 안에 넣어야 합니다.

**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.구조를 깔끔히 하라**
책에서는 수직/수평 구조에 대해서 설명합니다. 세로로는 코드를 신문처럼 중요도에 따라서 위-아래로 정렬하고, 사용처에서 가장 가까운 곳에 변수를 정의해야 합니다. 또 가로로는 코드가 화면을 벗어나지 않게 하고, 짧든 길든 항상 들여쓰기를 해야 클린한 코드가 됩니다.



**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을 발생시키기 때문에 더 깔끔합니다. 



이에 관련하여 클린한 객체 지향 프로그래밍을 위해 매우 중요한 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(냄새나는 코드)이라고 하며, 냄새를 감지하기 위해서 다양한 도구들이 존재합니다.