2025. 1. 8. 16:44ㆍ개발자 과정/Kotlin
기존 RPS 로직
fun play(userRps: RpsSelect): RpsResultResponse {
// 기존 게임 기록을 불러와 점수를 초기화
var userScore:Int=0
var comScore:Int=0
rpsDataRepository.findAll()
.let {
if(it.isNotEmpty()){
userScore=it.last().userScore
comScore=it.last().comScore
}
}
//게임 로직
val rpsBord = arrayOf(
arrayOf("무승부", "패배", "승리"),
arrayOf("승리", "무승부", "패배"),
arrayOf("패배", "승리", "무승부")
)
return try {
val comRps = RpsSelect.getRandom()
RpsResultResponse.from(
rpsDataRepository.save(
RpsData(
rpsBord[userRps.ordinal][comRps.ordinal]
.also {
if(it=="승리")++userScore
else if(it=="패배")++comScore
},
userRps.getMessage(),
comRps.getMessage(),
userScore, comScore
)
)
)
} catch (e: Exception) {
throw IllegalArgumentException("에러!")
}
}
기존의 Rps의 Play함수를 보면 전체적인 흐름을 보기 위해선 구체적인 부분들까지 들여다 봐야하는 단점이 있다.
이를 srp(단일책임 원칙)위배상태라고 한다.
그렇다면 우리가 srp를 준수하기 위해 어떤 접근을 해야 할까?
바로, 추상적 사고가 필요하다. 추상화라는 것은, 작게보면 abstract&virtual 문법이겠고, 크게 보면 세부사항들을 의미단위로 묶고, 큰 그림을 보는 과정이다.
또한, 이러한 추상적 사고를 통해 SRP를 적용하고, play의 매서드의 가독성을 높이기 위해 우리는 추가적인 발상이 필요하다. 이건 주변 동료들이 잘 놓치던 부분이기도 한데, "흐름도 책임" 이라는 것이다. 즉, 우리는 개발중 책임관리라는 것을 떠올리때, 구체적인 구현체들을 어떻게 쪼갤까 고민하는데, 중요한건 "흐름"이라는 추상적인 개념또한 하나의 책임으로 작용한다는 것이다. 그렇다면 이러한 생각을 가지고 리팩토링한 코드를 보자.
새로운 RPS 로직
override fun play(memberId:Long,userRps: RpsSelect): RpsResultResponse {
val comRps = RpsSelect.getRandom()
val score = getScore(memberId)
val result = getRpsResult(score, userRps, comRps)
return saveResult(memberId, result, userRps, comRps)
.let { RpsResultResponse.toDto(it) }
}
추상적 사고를 통해 구체적인 사항들을 의미단위로 묶어 책임을 매서드로 분리, 위임하고, play함수는 게임의 전반적인 "흐름"을 책임지게 되었다. 이렇게 된다면, 우리는 그 의미만 알면 되고, 구체적인 사항을 들여다 볼 필요가 없어진다.
그렇다. srp를 적용하려는 시도가 곧 "추상화" 가 된 것이다.
이렇게 되면 개발자는 사람중심적 사고로 편하게 읽으면 된다.
1. 컴퓨터에게서 램덤 값을 받고,
2. 기존 스코어를 불러온 다음,
3. 게임 결과를 받아오고,
4. 그걸 저장한 뒤 클라이언트에게 반환한다.
라는 흐름이 쉽게 읽한다. 즉, 가독성이 올라간 것이다.
이것이 제목에서 말한 "추상화는 곧 가독성이다" 라는 것을 설명한 것이다.
전체코드
@Service
class RpsService(
private val rpsDataRepository: RpsDataRepository
): RpsServiceImpl {
data class Score(var userScore: Int, var comScore: Int)
override fun play(memberId:Long,userRps: RpsSelect): RpsResultResponse {
val comRps = RpsSelect.getRandom()
val score = getScore(memberId)
val result = getRpsResult(score, userRps, comRps)
return saveResult(memberId, result, userRps, comRps)
.let { RpsResultResponse.toDto(it) }
}
override fun history(memberId: Long):List<RpsResultResponse>{
return rpsDataRepository.findByMemberId(memberId)
.map { RpsResultResponse.toDto(it) }
}
private fun getScore(memberId:Long): Score {
var userScore:Int=0
var comScore:Int=0
rpsDataRepository.findByMemberId(memberId)
.let {
if(it.isNotEmpty()){
userScore=it.last().userScore
comScore=it.last().comScore
}
}
return Score(userScore=userScore,comScore=comScore)
}
private fun getRpsResult(score: Score,userRps: RpsSelect,comRps: RpsSelect):Pair<String, Score> {
val rpsBord = arrayOf(
arrayOf("무승부", "패배", "승리"),
arrayOf("승리", "무승부", "패배"),
arrayOf("패배", "승리", "무승부")
)
return try {
val result = rpsBord[userRps.ordinal][comRps.ordinal]
.also {
if (it == "승리") ++score.userScore
else if (it == "패배") ++score.comScore
}
Pair(result, score)
} catch (e: Exception) {
throw IllegalArgumentException("Error!")
}
}
private fun saveResult(memberId: Long,result:Pair<String, Score>,userRps: RpsSelect,comRps: RpsSelect)
: RpsData {
return RpsData(
memberId,
result.first,
userRps.getMessage(),
comRps.getMessage(),
result.second.userScore,
result.second.comScore
).let { rpsDataRepository.save(it) }
}
}
추가적으로 보다시피 rpsService는 interface상속을 하도록 바뀌었다.
왜냐하면 추상화가 적용됨에 따라 controller에선 게임을 실행한 후 결과를 받고, 히스토리를 받아오기만 하면 될 뿐,
그것을 위한 구체적인 구현을 알 필요는 없기 때문이며, 접근해서도 서도 안되기 때문이다.
그래서 우리가 노출할 부분만 interface를 통해 제공하고, 나머지는 privete 캡슐화를 통해 은닉한다.
그럼 controller는 구체적인 구현을 알 필요 없이, interface만을 통해 충분히 요청을 처리할 수 있게 되는 것이다.
추상화 라는 것은 큰 의미로 보게되면, 우리가 무엇을 해야하는가 하는 "본질"이 보이고,
그것을 수면위로 드러내는 노력이 이루어지게 된다.
세부적인 사항들 속에 혼재되어 드러나지 않는 본질을 드러내는 과정이 곧, 추상화라고 할 수 있겠다.
'개발자 과정 > Kotlin' 카테고리의 다른 글
깃허브 파종기를 만들어 보자! (0) | 2025.02.24 |
---|---|
(kotlin-spring) twilio 문자 인증/문자 전송 서비스 만들기 (0) | 2025.02.12 |
springboot기초(4)-멤버를 추가해보자 (3) | 2024.10.03 |
springboot기초(3)-스코어와 히스토리를 저장해보자 (1) | 2024.09.26 |
springboot기초(2)-가위바위보를 고도화 해보자 (1) | 2024.09.24 |