SoftwareSystemDesign / simulated-evolution

Artificial Life Simulation of Bacteria Motion depending on DNA
https://java.woehlke.org/kurzweil/simulated-evolution/
Apache License 2.0
0 stars 6 forks source link

Refactoring smells #1

Open skele2k opened 1 year ago

skele2k commented 1 year ago

Places with smells

Cell

LifeCycle.java

This java file contains hard to maintain expressions to determine the stage of a cell. For example,

public LifeCycleStatus getLifeCycleStatus(){
        if(fat==0 && hunger>=0){
            return LifeCycleStatus.HUNGRY;
        }
        if(age<FULL_AGE){
            if(fat< FAT_MINIMUM_FOR_SEX){
                return LifeCycleStatus.YOUNG;
            } else {
                return YOUNG_AND_FAT;
            }
        } else {
            if (age<OLD_AGE) {
                return LifeCycleStatus.FULL_AGE;
            } else {
                if (age < MAX_AGE) {
                    return LifeCycleStatus.OLD;
                } else {
                    return LifeCycleStatus.DEAD;
                }
            }
        }
    }

Each expression can be a seperate method. For example,

public LifeCycleStatus isHungry() {
    if ( fat == 0 && hunger >= 0 ) {
        return LifeCycleStatus.HUNGRY;
    }
}

Neighbourhood

LatticePointNeighbourhood.java

We can apply template design to this section because there are lots of repeating codes for each neighbourhood types.

Before

uml_neighbourhood_before drawio

After

uml_neighbourhood drawio (1)

LatticePointNeighbourhoodPosition.java

We could get rid of the switch statement if we apply template design to the LatticePointNeighbourhood class. Instead of using seperate method for getting neighbourhood position, we can move the method to the LatticePointNeighbourhood class.

Before

public static LatticePointNeighbourhoodPosition[] getNeighbourhoodFor(
        LatticePointNeighbourhoodType neighbourhoodType
    ){
        LatticePointNeighbourhoodPosition[] result;
        switch (neighbourhoodType) {
            case VON_NEUMANN_NEIGHBORHOOD:
                result = new LatticePointNeighbourhoodPosition[4];
                result[0]=NORTH;
                result[1]=EAST;
                result[2]=SOUTH;
                result[3]=WEST;
                break;
            case MOORE_NEIGHBORHOOD:
                result = new LatticePointNeighbourhoodPosition[8];
                result[0]=NORTH_WEST;
                result[1]=NORTH;
                result[2]=NORTH_EAST;
                result[3]=EAST;
                result[4]=SOUTH_EAST;
                result[5]=SOUTH;
                result[6]=SOUTH_WEST;
                result[7]=WEST;
                break;
            case WOEHLKE_NEIGHBORHOOD:
                result = new LatticePointNeighbourhoodPosition[6];
                result[0]=NORTH_WEST;
                result[1]=NORTH;
                result[2]=NORTH_EAST;
                result[3]=EAST;
                result[4]=SOUTH_WEST;
                result[5]=WEST;
                break;
            default:
                result = new LatticePointNeighbourhoodPosition[1];
                result[0]=CENTER;
                break;
        }
        return result;
    }

After

public class LPVonNeumannNeighbourhood extends LatticePointNeighbourhood {
    public void getPosition() {};
}

LatticePointNeighbourhoodType.java

This class can be deleted

Food

SimulatedEvolutionWorldLattice.java

letFoodGrow() method has repeating logic thus we can split the logic into different methods.

koust6u commented 1 year ago

Model 패키지에 대한 정리

-Author

    2023/05/03 - 김민종


-Contents

    package by package 분석과 class by class의 수정고려 사항에 대한 정리.

0. 공통 고민 사항❗:

1. Lombok을 통한 setter,getter, constructor의 단순화가 과연 유지 보수 측면에서 필요한가?
$\rightarrow$ Lombok의 라인 수 줄여주는 효과가 trade off 될 만큼 명시화가 중요하단 생각은 안됨.

2. Generic 사용하여 범용성을 증가시키는 것이 옳은가?
$\rightarrow$ 이건 같이 고민해 보아야 할 것.

3. MAGIC_NUMBER들을 별도의 class나 annotation등으로 한곳에 모아둘 필요성이 있을까?
$\rightarrow$ 이것도 같이 고민해 보아야 할 것.


1. geometry

Abstract: Cell을 렌더링 하기 위한 좌표 정보와 크기 정보

1.1 LatticePoint (class)

description: 셀의 위치를 정보와 관련 연산을 제공함.
@Getter @Setter @ToString @NoArgsContstructor @AllArgsConstructor @EqualsAndHash
등과 같은 Lombok plugin을 통해 묵시적으로 생성자를 비롯한 데이터 접근 method를 생성함.
@Log4j를 통한 log 기능 제공 but never used. (아래 부턴 lombok 기능 과 Log4j 설명 생략.)

수정 고려 사항)
Mistype:

1.2 LatticeDimension (class)

description: cell의 width 와 height에 대한 정보와 연산 제공

수정 고려 사항)

Ambiguous:

1.3 LatticeRectangle (class)

description: LatticeDimension 과 LatticePoint의 정보를 통합.

수정 고려 사항)

2. Neighbourhood

Abstract: class 각각의 수정이 아닌 다형성을 바탕으로 package 전체의 구조적 변경이 필요해 보인다.

2.1 LatticePointNeighbourhoodType (Enum)

description: 인접 위치에 대한 정의 타입을 열거형으로 만듬.
참고:

수정 고려 사항)

2.2 LatticePointNeighbourhoodPosition (Enum)

description: LatticePointNeighbourhoodPosition의 type을 바탕으로 인접 좌표에 type에 맞게 채워나감.

수정 고려 사항) 1번의 LatticePointNeighbourhoodType과 함께 구조적 변경이 필요해 보임.

2.3 LatticePointNeighbourhood (class)

description: LatticePointNeighbourhood class는 Neighbourhood 패키지 전체의 구현체임.


3. Food

3.1 SimulatedEvolutionWorldLattice (class)

description: cell들이 소비할 수 있는 음식들을 난수성으로 생성해주는 역할로 보임.
또 cell들이 음식을 소비하는 것도 관여하는 것으로 보임. 따라서 SRP(단일 책임 원칙)을 지킬 수 있도록
class를 기능 단위로 분리해줄 필요성이 보인다.

수정 고려 사항)

mistype:

ambiguous:

Simplify:


4. Census

4.1 SimulatedEvalutionPoplationCensus (class)

description: 종류별 cell들의 갯수 data와 증가(increament) 로직을 가짐.
switch문 사용으로 추후 유지보수 측면에서의 단점을 가짐 하지만 해당 method가
LifeCycleStatus에 의존함으로 추후 LifeCyCleStatus와 함께 개선 고려 사항을 작성하겠다.

4.2 SimulatedEvalutionPoplationCensusContainer (class)

description: 세대별 cell들의 인구(?) 수치들을 stack에 저장한다.

(이 클래스는 ComputerKurzweilProperties 에 의존하고 멀티스레딩 환경을 고려하여
작성되었기에 건들이기 살짝 불안해서 조금더 분석해보고 업데이트 하겠습니다. ! )


5. Cell

Abstract: Cell들의 LifeCycle 관리 및 정보들을 총괄하여 관리해준다.

5.1 Orientation, LifeCyCleStatus(Enum)

description:

수정 고려 사항)

앞서 언급한 것과 같이 SimulatedEvalutionPoplationCensus는 LifeCycle에 의존적입니다.
또한 Enum LifeCyCle의 값을 받아와 SimulatedEvalutionPoplationCensus에 generation 별로 Cell들의
정보를 갱신 해 나가는 것을 알 수 있습니다. 하지만 SimulatedEvalutionPoplationCensus에서는 Switch문
을 통해 분류 후 증가 하는 방식을 사용하고 있습니다. 따라서 각각의 상태를 abstract class를 통해
다형성을 주고 빼서 template method 통해 증가 연산을 해줄지 다른 더 좋은 방법이 있는지 같이 고민해 봅시다.
왜냐하면 프로젝트 내에는 상당히 많은 Enum타입의 객체들을 사용합니다.
따라서 프로젝트의 코드 일관성을 위해 어떻게 개선 할지에 대한 팀원들 간의 토의가
필요해 보입니다.

👉 @TODO: Enum 타입의 대한 일관성에 대해 토의 해볼 것. 👈

5.2 LifeCycle (class)

description: > cell들의 lifeCycle에 관여하는 전역적인 정보들을 가지며, 상태 변경에도 관여한다.

수정 고려 사항)

ex1) 수정 전

public void eat(int food) {
   if (fat + food* FAT_PER_FOOD <= MAX_FAT) {
           fat += food* FAT_PER_FOOD;
   } 
   else {
           fat = MAX_FAT;
   }
}

수정 후

fat = (fat + food * FAT_PER_FOOD <= MAX_FAT) ? fat + food * FAT_PER_FOOD : MAX_FAT;

ex2) 수정 전

public boolean move() {
   age++;
   if (fat > 0) {
       fat--;
       return true;
   } 
   else {
       hunger++;
       return false;
   }
}

수정 후

public boolean move() {
   age++;
   return fat > 0 ? (fat-- > 0) : (Math.abs(hunger++) < 0 //항상 false가 되는 조건);
}

👉 @TODO: 코드 간략화와 가독성의 가중치에 대해 고민해 볼것. 👈

5.3 CellCore (class)

description: 본격적인 cell의 관리 로직으로 보임. 기능들로 세포 분열, dna증가,감소 로직을 가지고 있다.

개선 고려 사항)


Summary ✅

SOLID 관점(+일부의 예시):
1.전체적으로 SRP 원칙이 위배되고 있다.

 1. SimulatedEvolutionWorldLattice → food 생성, 그리고, 소비 등의 여러가지 책임을 가짐
 2. CellCore → Cell의 방향 생성, 세포분열, 세포 증감 등의 여러 책임을 가짐

2.OCP 및 LSP 원칙 보장을 위해 Strategy 패턴 또는 template method pattern 을 통해 보완 필요성 보임

1. LatticeDimension을 통해 여러가지 Lattice의 크기, 모양 변경에도 유연하게 적용 가능하도록 고민해보자.
  1. 상당히 많은 enumeration type 사용 대신 객체간의 결합도를 낮추고 OCP 원칙을 충족 시키기 위해 templateCall-back 등과 같은 변경에 유연한 패턴을 적용 시켜 보자.
dntks1942 commented 1 year ago

View 분석

날짜: 2023년 5월 7일

해당 프로그램을 실행시키면 다음과 같은 화면이 출력된다.

Screenshot_from_2023-05-07_01-30-06

해당 이미지를 구성하는 package가 바로 View package이다.

View package내의 Class분석

우선 기본적인 UML 구조는 다음과 같다.

Main

canvas

- CensusCanvas

- SimulatedEvolutionCanvas

census

- CensusElement

- CensusElementsPanelCounted

- CensusElementsPanelLifeCycle

panels

- CensusPanel

- CopyrightLabel

- SubTitleLabel

최종 Class

SimulatedEvolutionTab

Refactoring 관점에서 본 Class

Strategy Pattern

View에서 가능한 Refactoring을 고려해보면 Strategy Pattern을 이용하여 다른 형식의 그래프나 정보 표시를 출력하고 싶을 때 코드의 수정이 아닌 다른 형식의 class를 이용해서 출력을 다르게 할 수 있다.

이때 상단이 simulation 화면, 아래가 그래프 및 정보의 형식은 고정으로 둔다고 했을 때 CensusPanel는 그대로 두고 각 CensusCanvas을 Strategy Pattern을 사용해서 분리할 수 있을 것으로 보인다.

예를 들어서 CensusCanvas를 atrarct class로 변경 후 특정 ConcreteCenesusCanvas class로 분리해서 이를 CensusPanel에서 new ConcreteCenesusCanvas()로 인스턴스를 불러서 사용 가능하다.

적용 전 UML

ClassDiagram

적용 후 UML

ClassDiagram1

추가적으로 다른 화면 component들에 대해서도 변경이 예상되는 곳은 적용할 수 있을 것으로 예상된다.