코드를 쓸 때 하지 말아야 할 것들을 정리했다.
상수를 잘 써라
한 모듈이 여러 이유로 자주 수정되어야하면 복잡해진다.
예를들어 GameManager가 있을 때 그 GameManager가 PlayerData의 저장과 Gameplay의 흐름을 담당하고 있다고 생각해보자
이 경우 PlayerData를 수정하기 위해서 GameManager를 알아야 하며 자칫 잘못했다 버그가 터져서 수정중에 다른 사람이 같은 파일을 수정해서 충돌이 나는 등 문제가 많아진다.
이런 경우 자체를 제거를 위해 분리를 하면 애초에 이런 문제가 생기지 않는다.
샷건 수술
작은 변경을 위해 여러 곳을 동시에 수정해야하면 골치 아픔
예를들어 데미지 계산 시스템을 생각해볼 때 ApplyDamage, CalculateDamage, UpdateDamageLeaderboard 같은 함수를 여러 곳에 분리하면 하나씩 가서 수정해야 되는 문제가 생긴다
중간중간 어딨는지 까먹을 수도 있고 어딨는지 찾기 힘들 수도 있다.
차라리 아래와 같이 분리를 해서 한곳에서 정리하자는 거다.
class UDamageSystem : public UObject
{
ApplyDamage...
CalculateDamage...
UpdateDamageLeaderboard...
};
기능 편애
어떤 함수가 자기 객체보다 남의 객체 기능이나 데이터와 더 많이 소통한다면
그 함수를 데이터가 있는 곳으로 옮겨 의존성을 줄이자
데이터 뭉치
자주 함께 쓰이는 데이터는 하나로 묶으면 의미가 명확해짐
중복되는 필드나 매개변수 그룹은 별도 구조로 분리하자.
비슷한 데이터끼리 모아놓자
예를 들어 아래와 같이 매개변수가 반복된다 생각해보자
FireWeapon(float Damage, float Range, float Accuracy)
ShowWeaponInfo(float Damage, float Range, float Accuracy)
이걸 아래와 같이 struct로 정리 해버리면 훨씬 하나의 뜻으로써 정리가 된다는 거다.
struct FWeaponInfo
{
public:
float Damage;
float Range;
float Accuracy;
};
FireWeapon(const FWeaponInfo& data)
ShowWeaponInfo(const FWeaponInfo& data)
기본형 집착
복잡한 데이터를 단순한 기본형에 과도하게 의존하는 경향
복잡한 개념은 기본형 대신 차라리 클래스나 구조체를 사용해서 해결하자
타입 설계를 통해 복잡한 로직과 버그 발생의 가능성을 줄여줄 수 있다.
반복되는 스위치문
새로운 분기가 생길 때마다 여러 switch문을 전부 수정해야 한다면 비효율적이다.
다형성 구조를 적용해 중복되는 분기 로직을 없애자. 되도록이면 switch 쓰지 말고
반복문
루프 안에 비즈니스 로직을 다 넣지 말자
중첩 반복문은 왠만하면 피해야한다.
성의 없는 요소
코드 흐름상 실제로 필요 없는 구조는 과감히 없애자. 지우기 귀찮아도 삭제하자
추측성 일반화
현재 필요한 기능에 집중해 불필요한 추상화를 걷어내자
미래 대비보다 현재 문제 해결이 우선
나중에 필요할 수도 있다는 생각으로 만든 코드는 대부분 짐이 된다.
임시 필드
목적이 분명치 않은 필드는 코드 복잡도를 높이는 원인이다
특정 상황에서만 쓰이는 필드는 다른 상황에선 쓸데없는 혼란만 부를 뿐이다.
예를들어
class AEnemy : public UActor
{
public:
float Health;
//원거리 적만 씀
float ProjectileSpeed;
//특정 몬스터만 씀
float TeleportCooldown;
};
이런식으로 있으면 특정 객체에만 사용하는데 변수가 쓰지 않는 변수가 있어 더 헷갈리기만 한다.
차라리 아래처럼 Component로 분리하는게 훨씬 좋다.
class UProjectileComponent : public UActorComponent
{
public:
float ProjectileSpeed;
void Fire();
};
class UTeleportComponent : public UActorComponent
{
public:
float TeleportCooldown;
void Teleport();
};
class AEnemy : public UActor
{
public:
float Health;
UPorjectileComponent* ProjectileComp;
UTeleportComponent* TeleportComp;
};
메시지 체인
객체를 줄줄이 호출하면 내부 구조가 노출돼 결합도가 커진다.
필요하다면 최종 로직을 호출부 가까이로 옮겨 의존을 줄이자
중재자
너무 많은 디자인 패턴들을 도입하다 보면 해당 클래스가 연결만 해주고 아무것도 안하는 일이 생길 수 있다.
실질적 로직 없이 위임만 하는 클래스는 존재할 필요가 없다.
ㅁㄴㅇ
늘 직관적 구조로 수정하자
내부자 거래
모듈 간에 비공개 데이터가 과하게 오가면 결합도가 높아짐
필요한 정보만 교환할 수 있게 인터페이스 범위를 명확히 정의하자
모듈 간 벽을 두껍게 유지해 각자의 책임을 분리하자
거대한 클래스
너무 많은 책임을 지는 클래스는 필드와 메서드가 폭발적으로 늘어난다.
중복이 생기고 관리가 어려워지니 분리하자
서로 다른 인터페이스의 대안 클래스들
클래스를 교체하려면 인터페이스가 호환되어야한다
유사 기능 클래스끼리 일관된 형식을 갖추는 것이 좋음
메서드 시그니처를 통일해 교체 가능성을 올리자
데이터 클래스
필드와 getter/setter만 있는 클래스는 다른 곳에서 함부로 조작되기 쉬움
변경될 필요가 없는 필드는 세터를 제거해 안전성을 높이자
필요 기능이 있다면 이 클래스 안에 직접 구현해 응집도를 높이자
상속 포기
서브클래스가 부모의 기능 중 일부만 필요하거나 인터페이스가 맞지 않는다면 꼭 상속하지 않고, 필요한 부분만 다른 방식으로 얻을 수 있다.
위임 등으로 불필요한 유산을 거부해 구조를 단순화하자.
주석의 남용
올바른 주석은 아주 좋지만, 코드만으로 명확하게 이해되는게 더 좋다.
주석이 필요한 상황일 경우, 주석이 필요없는 코드로 먼저 바꾸는게 우선이다.
계속 짠 코드를 생각하며 어떤게 더 나은 구조가 없었을까? 생각하며 짜는게 좋다.
근데 경험상 일단 돌아가는게 먼저더라 항상 코드를 짜며 생각해되, 거기에 매몰되지는 말자