Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BE] 이근표 강화하기 #2

Open
wants to merge 16 commits into
base: rootTiket
Choose a base branch
from

Conversation

rootTiket
Copy link
Member

@rootTiket rootTiket commented May 29, 2024

❓ 어떤 기능을 구현하였나요?


✅ 회원가입 기능 구현

POST users/register

결과 사진
스크린샷 2024-05-29 오전 12 30 40

회원가입 기능을 구현합니다. 사용자의 이메일, 이름, 비밀번호를 입력받아 회원가입을 진행합니다.
중복 로그인 시 예외를 처리했습니다.

✅ 로그인 기능 구현

POST users/login

결과 사진
스크린샷 2024-05-29 오전 12 30 51

로그인 기능을 구현합니다. 사용자의 이메일, 비밀번호를 입력받아 로그인을 진행합니다. 만약 아이디나 비밀번호가 틀릴경우 예외를 처리했습니다.

✅ 강화기능 구현

POST /enhance

결과 사진
스크린샷 2024-05-29 오전 12 32 34
강화권 소진 시 예외
스크린샷 2024-05-29 오전 12 32 42

강화기능을 구현합니다. requestBody의 강화권 사용 유무에 따라 달라지는 확률을 처리했습니다.
확률 강화권을 모두 사용했을경우 예외메시지도 따로 처리했습니다.

✅ 아이템 생성 구현

POST /items

결과 사진
스크린샷 2024-05-29 오전 12 32 16

아이템 생성을 구현합니다. 아이템 이름을 요청받아 db에 저장합니다. 로그인을 하지 않았을 경우 예외를, 이미 아이템을 가지고 있을 경우 예외를 각각 처리하였습니다.

✅ 아이템 조회 구현

GET /items

결과 사진
스크린샷 2024-05-29 오전 12 32 58

아이템 조회를 구현합니다.

✅ 아이템Top10 조회 구현

GET /items/top10

결과 사진
스크린샷 2024-05-29 오전 12 37 59

아이템 Top10 조회를 구현합니다.


🤔 구현 중 무엇을 고민했을까요?

강화로직을 어떻게 분리해야 할까?


최초 구현 코드
public EnhanceResponse executeEnhance(Authentication authentication, EnhanceRequest request) {
        User user = userRepository.findByEmail(authentication.getName()).orElseThrow(UserNotFoundException::new);
        Weapon weapon = weaponRepository.findByUser(user).orElseThrow(WeaponNotFoundException::new);
        Random random = new Random();
        Integer increase = ((isUsedIncreaseProbability(user,request.useIncreaseProbability()))? 30 : 0);
        WeaponResponseMessage weaponResponseMessage;
        Integer level = weapon.getLevel();
        Level currentLevel = getValidLevel(level);
        if (random.nextInt(100) < currentLevel.getSuccessRate()+increase) {
            level = weapon.getLevel() + 1;
            weaponResponseMessage = UPGRADE_SUCCESS;
        } else {
            weaponResponseMessage = UPGRADE_FAILED;
            if (random.nextInt(100) < Math.min(currentLevel.getDestroyRate(), MAX_DESTROY_RATE)) {
                weaponResponseMessage = LEVEL_DOWN;
                level = weapon.getLevel() - 1;
                if (random.nextInt(100) < 50) {
                    weaponResponseMessage = DESTROYED;
                    level = 0;
                }
            }
        }
        weaponRepository.save(Weapon.updateWeapon(weapon, level));
        return new EnhanceResponse(weaponResponseMessage, level);
    }

아무리 봐도 읽기 쉽지 않은 코드

기존 강화는 weaponEnhance(아이템 관련 로직을 처리하는 함수) 에 모두 구현되어 있었습니다.


함수 분리
private EnhanceResponse enhanceWeapon(Weapon weapon, Integer increase) {
        Random random = new Random();
        Integer level = weapon.getLevel();
        Level currentLevel = getValidLevel(level);

        if (random.nextInt(100) < currentLevel.getSuccessRate() + increase) {
            return new EnhanceResponse(UPGRADE_SUCCESS, level + 1);
        } else {
            return handleFailure(random, level, currentLevel);
        }
    }

    private EnhanceResponse handleFailure(Random random, Integer level, Level currentLevel) {
        if (random.nextInt(100) < Math.min(currentLevel.getDestroyRate(), MAX_DESTROY_RATE)) {
            level--;
            if (random.nextInt(100) < 50) {
                return new EnhanceResponse(DESTROYED, 0);
            } else {
                return new EnhanceResponse(LEVEL_DOWN, level);
            }
        }
        return new EnhanceResponse(UPGRADE_FAILED, level);
    }

    private Integer calculateIncreaseProbability(User user, EnhanceRequest request) {
        return isUsedIncreaseProbability(user, request.useIncreaseProbability()) ? 30 : 0;
    }

    public Boolean isUsedIncreaseProbability(User user, Boolean useIncreaseProbability) {
        if (useIncreaseProbability && user.getIncreasingProbability() <= 0) {
            throw new NoItemException();
        } else if (useIncreaseProbability) {
            userRepository.save(User.decreaseItem(user));
            return true;
        } else {
            return false;
        }
    }

가독성도, 유지보수도 어려운 코드 인 것 같아 확률을 얻어오는 함수, 강화를 담당하는 함수, 강화실패시 로직을 처리하는 함수를 분리하자는 생각으로 리펙토링을 진행하였습니다.


클래스 분리
// weaponService
public EnhanceResponse executeEnhance(Authentication authentication, EnhanceRequest request) {
        User user = userRepository.findByEmail(authentication.getName()).orElseThrow(UserNotFoundException::new);
        Weapon weapon = weaponRepository.findByUser(user).orElseThrow(WeaponNotFoundException::new);
        Integer increase = calculateIncreaseProbability(user, request);
        EnhanceResponse result = weapon.enhance(increase);
        weaponRepository.save(weapon);
        return new EnhanceResponse(result.weaponResponseMessage(), result.level());
    }

// weaponEntity
public EnhanceResponse enhance(Integer increaseProbability) {
        Random random = new Random();
        Level currentLevel = Level.getLevelByNumber(this.level).get();

        if (random.nextInt(RANDOM_BOUND) < currentLevel.getSuccessRate() + increaseProbability) {
            this.level++;
            return new EnhanceResponse(UPGRADE_SUCCESS, this.level);
        } else {
            return handleFailure(random, currentLevel);
        }
    }

    private EnhanceResponse handleFailure(Random random, Level currentLevel) {
        if (random.nextInt(RANDOM_BOUND) < Math.min(currentLevel.getDestroyRate(), MAX_DESTROY_RATE)) {
            if (random.nextInt(RANDOM_BOUND) < 50) {
                this.level = 0;
                return new EnhanceResponse(DESTROYED, this.level);
            } else {
                this.level--;
                return new EnhanceResponse(LEVEL_DOWN, this.level);
            }
        } else {
            return new EnhanceResponse(UPGRADE_FAILED, this.level);}
    }

💭 어차피 강화는 무기(아이템)의 수치를 변경하는 것 아닌가? 클래스를 분리해보자!


💭 아쉬운 점은?

스트림, 람다를 공부 하고 있어 최대한 활용해보려고 노력했습니다. 또한 기존이라면 서비스에서 처리해야 할 것 같다 하는 부분도 더 고민해 보며 객체에서 변경 가능한가? , 이걸 서비스에서 처리하는게 맞나? 를 고민해보며 코드를 짰던 것 같습니다.
클래스 분리 한 강화 코드에서 반환 형태를 ResponseDto로 넘기는 부분은 코드의 의미가 명확하지 못해서 다른 클래스로 전달하여 처리하는 로직으로 수정하려고 합니다.

@rootTiket rootTiket self-assigned this May 29, 2024
@rootTiket rootTiket changed the title [BE] 이근표 강화게임 [BE] 이근표 강화하기 May 29, 2024
Copy link
Member

@hoonyworld hoonyworld left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다!


@Getter
@NoArgsConstructor
@AllArgsConstructor
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

매개변수가 있는 생성자를 대체하기 위해 정적 팩토리 메서드를 사용하신 것이라면, 해당 어노테이션은 필요가 없는 어노테이션이라 생각되는데 혹시 어떤 이유로 사용하셨을까요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

구현된 정적 팩토리는 유저가 가지고 있는 확률아이템의 개수를 감소시키는 메서드 였습니다!

import static org.springframework.http.HttpStatus.OK;

@RestController
@EnableJpaAuditing
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@EnableJpaAuditing를 컨트롤러에 어노테이션을 붙여주신 이유가 궁금합니다!

저는 JpaAuditingConfig라는 auditing 기능을 담당하는 클래스를 만들고, 이 클래스에 해당 어노테이션을 붙여주는데 그 이유는 다음과 같습니다.
한 번 참고해 보시면 좋을 것 같아요!

https://hellorennon.tistory.com/9

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지적 감사합니다! 수정하였습니다!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UserResponseMessage enum 에서는 @Getter와 @AllArgsConstructor로 처리해주셨는데, 해당 클래스에서는 직접 getter와 생성자를 만드신 이유가 궁금합니다

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

서비스로직 중 level 객체에서 정보를 가져와야 하는 부분이 있어 필요에 의해 만들었습니다!

Copy link
Member

@KoungQ KoungQ left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니당

public User checkValidUser(LoginRequest loginRequest) {
if (!(userRepository.findByEmail(loginRequest.email()).isPresent())) {
throw new InvalidIdException();
} else if (!(userRepository.findByPassword(loginRequest.password())).isPresent()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해싱도 하면 좋을 거 같아요 :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

누락된것 같습니다 감사합니다!

Comment on lines +37 to +40
public static Optional<Level> getLevelByNumber(int levelNumber) {
return Arrays.stream(values())
.filter(level -> level.getLevel() == levelNumber)
.findFirst();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7레벨 넘게 된다면 리턴 값이 null 일 것 같아요

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

로직 분리 중 누락되었네요 감사합니다

private static final int MAX_DESTROY_RATE = 50;

public void createWeapon(Authentication authentication, CreateWeaponRequest createWeaponRequest) {
User user = userRepository.findByEmail(authentication.getName()).orElseThrow(UserNotFoundException::new);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

User 객체를 가져오는 로직이 메서드마다 동일하게 존재하는데 컨트롤러 내부에서 하나의 메서드로 뺴두고 호출하면 중복도 피하고 가독성 면에서도 좋을 것 같아요!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

참고하겠습니다 !

Comment on lines +9 to +19
@Slf4j
@RestControllerAdvice
@RequiredArgsConstructor
public class GlobalExceptionHandler {
private static final String LOG_FORMAT = "Class : {}, Code : {}, Message : {}";
@ExceptionHandler(ServiceException.class)
public ResponseDto handleApplicationException(ServiceException ex) {
log.error(LOG_FORMAT, ex.getClass().getSimpleName(), ex.getErrorCode(), ex.getMessage());
return ResponseDto.of(ex.getErrorCode(), ex.getMessage());
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

덕분에 저도 컨트롤러를 간결하게 작성할 수 있었어요
에러는 다른 곳에서 핸들링 하는 게 보기 좋네요

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

감사합니다 :)

Comment on lines +59 to +67
if (useIncreaseProbability && user.getIncreasingProbability() <= 0) {
throw new NoItemException();
} else if (useIncreaseProbability) {
userRepository.save(User.decreaseItem(user));
return true;
} else {
return false;
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

얼리리턴 형식으로 바꿀 수 있을거 같아요!

Comment on lines +73 to +82
if (random.nextInt(RANDOM_BOUND) < 50) {
this.level = 0;
return new EnhanceResponse(DESTROYED, this.level);
} else {
this.level--;
return new EnhanceResponse(LEVEL_DOWN, this.level);
}
} else {
return new EnhanceResponse(UPGRADE_FAILED, this.level);}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이것도 얼리리턴 형식으로 바꾸면 더 깔끔할 것 같습니다!

Copy link
Member

@leejuae leejuae left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

최고!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants