가. 이해하기 어렵게 만드는 분기중첩
조건 분기는 조건에 따라 처리 방식을 다르게 하는데 사용되는 프로그래밍 언어의 기본 제어 구조입니다. 그런데 조건 분기를 어설프게 사용하면 악마가 되어 개발자를 괴롭힙니다.
잘못된 예
if ( 0 < member.hitPoint ) { //살아 있는가?
if (member.canAct()) { //움직일 수 있는가?
if(magic.costMagicPoint <= member.magicPoint) { //매직포인트가 있는가?
member.consumeMagicPoint(magic.costMagicPoint);
member.chant(magic);
}
}
}
위와 같이 중첩이 많을수록 가독성이 나빠집니다.
1) 우선 중첩을 걷어내는 데 방법중 하나는 조기 리턴이 있습니다.
if (member.hitPoint <= 0) return;
if (!member.canAct()) return;
if (member.magicPoint < magic.costMagicPoint) return;
member.consumeMagicPoint(magic.costMagicPoint);
member.chant(magic);
위와 같이 코딩하면 조건 추가가 심플해집니다. 또는 실행 로직 추가가 간단해집니다.
다른 예를 보시지요
flaot hitPointRate = member.hitPoint / member.maxHitPoint;
if(hitPointRate == 0){
return HealthCondition.dead;
}
else if (hitPointRate < 0.3){
return HealthCondition.danger;
}
else if (hitPointRate < 0.5){
return HealthCondition.caution;
}
else {
return HealthCondition.fine;
}
아래와 같이 바로 리턴으로 풀면 else if가 필요가 없습니다.
float hitPointRate = member.hitPoint / member.maxHitPoint;
if (hitPointRate == 0) return HealthCondition.dead;
if (hitPointRate < 0.3) return HealthCondition.danger;
if (hitPointRate < 0.5) return HealthCondition.caution;
return HealthCondition.fine;
switch 조건문을 사용해서 코드 작성하는 경우에는 다음과 같은 이슈가 발생합니다.
잘못된 예
enum MagicType {
fire,
lighting
}
class MagicManager {
String getName(MagicType magicType){
String Name = "";
switch (magicType) {
case fire:
name = "파이어";
break;
case lightning:
..
}
return name;
}
}
int costMagicPoint(MagicType magicType, Member member){
int magicPoint = 0;
switch (magicType){
case fire:
magicPoint = 2;
break;
case lightening:
magicPoint = 5 + (int)(member.level * 0.2);
break;
}
return magicPoint;
}
int attackPower(MagicType magicType, Member member){
int attackPower = 0;
switch (maticType) {
case fire:
attackPower = 20 + (int)(member.level * 0.5);
break;
case lightening:
attackPower = 50 + (int)(member.level * 1.5);
break;
}
return attackPower;
}
만약 이런 코드에서 새로운 마법 '헬파이어'가 추가된다면 개발자는 여러군데 case를 추가해야 하는 번거로움이 있습니다.
이때는 우선 조건 분기를 모읍니다. 단일 책임 선택의 원칙 을 적용합니다.
한번에 switch 구문을 작성하는 것입니다.
class Magic {
final String name;
final int costMagicPoint;
final int attackPower;
final int costTechnicalPoint;
Magic(final MagicType magicType, final Member member){
switch (magicType) {
case fire:
name = "파이어";
costMagicPoint = 2;
attackPower = 20 + (int)(member.level * 0.5);
costTechinicalPoint = 0;
break;
case lightening: ...
default:
throw new IllegalArgumentException();
}
}
}
2) 이렇게도 좋지만, 더 유지관리가 쉽도록 간결하게 바꾸어 봅시다. 여기서는 인터페이스를 쓰겠습니다. 인터페이스는 하위 상속 클래스들을 같은 자료형으로 사용할 수 있으므로, 굳이 자료형을 판정하지 않아도 됩니다.
올바른 예
interface Magic {
String name();
MagicPoint costMagicPoint();
AttackPower attackPower();
TechnicalPoint costTechnicalPoint();
}
위아 같이 인터페이스화 하여 규격화하면 Magic의 구현체에서 메서드를 빠뜨리는 실수를 방지할 수 있습니다.
그리고 추가적으로 값객체화하여 자료형의 고민도 없도록 만들었습니다.
class Fire implements Magic {
private final Member member;
Fire(final Member member){
this.member = member;
}
public String name(){
return "파이어";
}
public MagicPoint costMagicPoint(){
return new MagicPoint(2);
}
public AttackPower attackPower() {
final int value = 20 + (int)(member.level * 0.5);
return new AttackPower(value);
}
public TechnicalPoint costTechnicalPoint() {
return new TechnicalPoint(0);
}
}
이런식으로 라이트닝, 헬파이어 Class를 작성한다...
그리고 이제 switch 조건문이 아니라 Map으로 변경한다.
final Map<MagicType, Magic> magics = new HashMap<>();
final Fire fire = new Fire(member);
final Lightning lightning = new Lightning(member);
final HellFire hellFire = new HellFire(member);
magics.put(MagicType.fire, fire);
magics.put(MagicType.lightning, lightning);
magics.put(MagicType.hellFire, hellFire);
final Map<MagicType, Magic> magics = new HashMap<>();
final Fire fire = new Fire(member);
final Lightning lightning = new Lightning(member);
final HellFire hellFire = new HellFire(member);
magics.put(MagicType.fire, fire);
magics.put(MagicType.lightning, lightning);
magics.put(MagicType.hellFire, hellFire);
void magicAttack(final MagicType magicType) {
final Magic usingMagic = magics.get(magicType);
showMagicName(usingMagic);
consumeMagicPoint(usingMagic);
consuemTechnicalPoint(usingMagic);
magicDamage(usingMagic);
}
void showMagicName(final Magic magic){
final String mame = magic.name();
}
void consumeMagicPoint(final Magic magic) {
final int costMagicPoint = magic.costMagicPoint();
}
void consumeTechnicalPoint(final Magic magic) {
final int costTechnicalPoint = magic.costTechnicalPoint();
}
void magicDamage(final Magic magic) {
final int attackPower = magic.attackPower();
}
'Programming > 기타' 카테고리의 다른 글
코드 품질올리기, 코드 설계 - 2 분기중접 줄이기 (3/3) (0) | 2023.08.28 |
---|---|
코드 품질올리기, 코드 설계 - 2 분기중접 줄이기 (2/3) (0) | 2023.08.28 |
코드 품질올리기, 코드 설계 - 1 이름의 중요성과 모델링 (0) | 2023.08.28 |
어플리케이션 구성요소별 Naming Convention (0) | 2023.06.26 |
GraphQL Apollo 서버와 MySQL 연동 (0) | 2023.04.01 |