Clean Code · Ch.10

Clean Classes

클래스 응집도

메서드 대부분이 공통된 인스턴스 변수를 사용할수록 응집도가 높다.

응집도란 — 클래스 내부의 데이터와 메서드들이 하나의 논리적인 책임을 중심으로 서로 밀접하게 관련돼 있는 정도.
✅ 높은 응집도 Stack
public class Stack
{
    private int _topOfStack = 0;
    private List<int> _elements = new List<int>();

    public int Size() => _topOfStack;

    public void Push(int element)
    {
        _topOfStack++;
        _elements.Add(element);
    }

    public int Pop()
    {
        if (_topOfStack == 0)
            throw new InvalidOperationException("Stack is empty.");
        int element = _elements[--_topOfStack];
        _elements.RemoveAt(_topOfStack);
        return element;
    }
}

Size(), Push(), Pop() 모두 _topOfStack과 _elements를 함께 사용한다.

🎯

클래스가 오직 '스택'이라는 하나의 개념을 중심으로 구성되어 있다.

📌

인스턴스 변수가 혼자 노는 경우가 없다. 책임이 명확하다.

⚠️ 응집도가 낮아지는 시나리오
⚠️

큰 함수에서 일부를 별도 함수로 추출하려는데, 그 부분이 지역 변수를 4개 사용한다. 이때 그 변수들을 클래스 인스턴스 변수로 승격하면…

지역 변수 4개
클래스 변수로 승격
일부 함수만 사용하는 변수 발생
응집도 저하
독립 클래스로 분리
응집도가 낮아진다는 신호 — 일부 메서드만 사용하는 인스턴스 변수가 생기기 시작하면, 그 함수들을 독자적인 클래스로 분리할 때가 된 것이다.

변경에 유연한 클래스 설계

SRP를 지키면 변경이 필요한 지점에만 수정이 국소화된다.

❌ SRP 위반 모든 SQL이 한 클래스에
public class Sql {
    public Sql(String table, Column[] columns)
    public String create()
    public String insert(Object[] fields)
    public String selectAll()
    public String findByKey(String keyColumn, String keyValue)
    public String select(Column column, String pattern)
    public String preparedInsert()
    // private 메서드들...
}

SRP 위반: SQL 생성과 관련된 모든 책임이 하나의 클래스에 있다.

🔀

변경 이유 다수: CREATE 변경, INSERT 변경, SELECT 변경 — 각기 다른 이유로 이 클래스를 수정하게 된다.

🔗

강한 결합: insert(), select(), findByKey()는 다른 동작인데 같은 클래스에 묶여 있다.

✅ STEP 1 abstract base class
// STEP 1: abstract base class
public abstract class Sql
{
    protected readonly string Table;
    protected readonly Column[] Columns;

    protected Sql(string table, Column[] columns)
    {
        Table = table;
        Columns = columns;
    }

    public abstract string Generate();
}
✅ STEP 2 역할별 구체 클래스
// STEP 2: 역할별 구체 클래스
public class CreateSql : Sql
{
    public CreateSql(string table, Column[] columns) : base(table, columns) { }

    public override string Generate()
    {
        var cols = string.Join(", ", Columns.Select(c => $"{c.Name} {c.Type}"));
        return $"CREATE TABLE {Table} ({cols});";
    }
}

public class InsertSql : Sql
{
    private readonly object[] _values;

    public InsertSql(string table, Column[] columns, object[] values)
        : base(table, columns) { _values = values; }

    public override string Generate()
    {
        var columnList = string.Join(", ", Columns.Select(c => c.Name));
        var valueList = string.Join(", ", _values.Select(v => $"'{v}'"));
        return $"INSERT INTO {Table} ({columnList}) VALUES ({valueList});";
    }
}
// SelectSql, FindByKeySql 등도 동일하게...
OCP 달성 — 새로운 SQL 구문을 추가할 때 기존 클래스는 건드리지 않고, 새 클래스만 추가하면 된다. 변경이 필요한 지점이 명확해진다.

Ch.10 핵심 원칙 요약

응집도 높은 클래스 설계와 변경에 유연한 구조

🧲

높은 응집도

메서드 대부분이 같은 인스턴스 변수를 사용. 하나의 목적으로 구성.

✂️

클래스 분리 신호

일부만 쓰는 변수가 생기면 독립 클래스로 분리하라.

🔒

변경에 닫혀 있어야

기존 클래스를 수정하지 않고 새 클래스로 확장 가능하게 (OCP).

🎯

SRP — 변경 이유 하나

클래스는 오직 하나의 이유로만 변경되어야 한다.

응집도 → 분리 → OCP — 응집도를 높이려다 보면 자연스럽게 클래스가 분리되고, 그 결과 변경에 유연한 설계가 된다.