스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 - 1 Kotlin Code Refactoring (김영한님 강의)

박준규·2023년 1월 24일
0

Java & Spring

목록 보기
2/4

Spring 공부에 진전이 없기도 하고.. 유튭 영상보다가 우아콘에서 kotlin 실력을 늘릴려면, Java 코드를 Kotlin으로 Refactoring 하면 직방이라고 해서 아주아주 기초부터 다시 시작하자는 마음으로 김영한 강사님의 스프링 입문 강의부터 시작해서 Java코드를 Kotlin으로 바꿔보자는 다짐을 했다. 얼마나 갈지는 모르지만, 그래도 꾸준히 해보자...

그럼 바로 바꾸다가 기록할 만한 것부터 시작!

1. Optional은 Kotlin에서 굳이 쓰지 않아도 된다.

코드를 바꾸면서 Java SpringBoot의 경우 Optional로 Null값을 컨트롤하는 것을 보았다. 근데 굳이 Kotlin에서는 Optional 객체를 사용하지 않아도 된다. 무엇보다 Java에서 Kotlin으로 refactoring하는 이유가 코드 가독성도 어느 정도 포함되어 있으면서, Kotlin 문법을 최대한 활용해야 하는데, 코드가 거의 같다면 Refactoring 할 이유가 없으니 말이다.

따라서 강의에 나온 Repository

import hello.hellospring.domain.Member;
import java.util.List;
import java.util.Optional;

public interface MemberRepository {

    Member save(Member member);
    
    Optional<Member> findById(Long id);
    
    Optional<Member> findByName(String name);
    
    List<Member> findAll();

}

는 Kotlin으로 다음과 같이 변경할 수 있다.

package kotlinhellospring.demo.repository

import kotlinhellospring.demo.domain.Member

interface MemberRepository {

    fun save(member: Member): Member

    fun findById(id: Long): Member?

    fun findByName(name: String): Member?

    fun findAll(): List<Member>
}

?를 통해 Null값을 체크할 수 있으니, 굳이 Optional을 사용하지 않아도 된다.

또한 Kotlin은 코드를 java로 바꾸면 생각보다 많은 내용이 함축되어 있다. 이는 kotlin → java로 변경하면 바로 보인다.

public interface MemberRepository {
   @NotNull
   Member save(@NotNull Member var1);

   @Nullable
   Member findById(long var1);

   @Nullable
   Member findByName(@NotNull String var1);

   @NotNull
   List findAll();
}

위 코드는 kotlin코드를 java로 Decompile 한 것이다. kotlin에서의 ? 여부에 따라 Java @Notnull @Nullable이 생성되는 것을 확인 할 수 있다.

그리고 MemberRepository 구현체 중 Optional이 사용된 부분을 보자.

java를 보면 다음과 같다.

    @Override
    public Optional<Member> findByName(String name) {

        return store.values()
                .stream()
                .filter(
                        member -> member.getName().equals(name)
                )
                .findFirst();
    }

위와 같은 코드를 Kotlin에서는 아래와 같이 표현할 수 있다.

    override fun findByName(name: String): Member? = store
        .values
        .find {
	        member -> member.name == name
        }

kotlin의 find는 찾는 즉시 해당 첫번 째 객체를 반환한다.

그리고 또한 java처럼 Stream에 굳이 올릴 필요가 없다. kotlin 내부에서 모두 처리 가능하기 때문에.. 왜냐면 java에서 Stream으로 올리는 이유가 lambda를 사용하여 코드 가독성과 함수형 코드를 진행하기 위해서인데, kotlin은 모두 지원해주기 때문이다.

2. Kotlin Test 코드 작성 시 주의! Repository, Component Annotation으로 Bean 등록 및 SpringBootTest, Autowired Constructor Annotation 사용.

난.. 말하는 감자도 아닌, 말도 못하는 감자다.. 해당 문제로 인해 몇 시간 동안 답을 찾으러 Google과 씨름했다..ㅠ

그거슨 바로 아래의 오류...!!

No ParameterResolver registered for parameter in constructor.......

Kotlin에서는 왜인지는 모르겠는데, Bean 등록이 잘 안..ㅎㅎ

그래서 구현한 부분을 Bean으로 등록하려면 @Component를 사용하던, @Repository를 사용하던 @Service를 사용하던 Bean으로 등록해야 한다.

또한 Test할 클래스 위에 @SpringBootTest를 붙여준다. 거기에 사용할 Bean으로 등록된 구현체를 생성자로 활용하기 위해서는

@Autowired constructor를 지정해준다. 우선 java 코드는 다음과 같다.

Java Code

MemoryMemberRepository class 파일

import hello.hellospring.domain.Member;
import java.util.*;

public class MemoryMemberRepository implements MemberRepository {
	private static Map<Long, Member> store = new HashMap<>();
    private static long sequence = 0L;
	...
}

java는 @SpringBootTest를 붙이지 않아도 된다. AutoWired할 필요도 없다. 또한 아래 처럼

MemoryMemberRepositoryTest class 파일

import hello.hellospring.domain.Member;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

class MemoryMemberRepositoryTest {

    MemoryMemberRepository memoryMemberRepository = new MemoryMemberRepository();

    @Test
    public void save() {
        ...
        // 여기서 테스트를 그냥 진행해도 아무런 문제가 없다.
    }

}

굳이 Bean으로 등록하지 않아도 알아서 관리해준다.

이제 Kotlin을 보자.

Kotlin Code

MemoryMemberRepository class 파일

import kotlinhellospring.demo.domain.Member
import org.springframework.stereotype.Repository
import java.util.*
import kotlin.collections.ArrayList
import kotlin.collections.HashMap

/**
 * bean으로 등록하여 관리하려면
 * Component 또는 Repository Annotation을 사용하여 등록하면 된다.
 * 테스트 코드가.. 안ㄷ...
 */
@Repository
class MemoryMemberRepository : MemberRepository {

    companion object {
    	/**
         * 가변성 제어는 다음 시간에
         */
        var store: HashMap<Long, Member> = hashMapOf()
        var sequence: Long = 0L
    }
}

위 구현체를 Bean으로 등록하기 위해 @Repository 또는 @Componenet를 사용한다.

MemoryMemberRepositoryTest class 파일

import kotlinhellospring.demo.domain.Member
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest

@SpringBootTest
class MemoryMemberRepositoryTest @Autowired constructor(
    private val memoryMemberRepository: MemoryMemberRepository
) {

    @Test
    fun save() {
        ...
    }

}

또한 Test룰 하겠다는 명시적 의미와 생성자 Parameter를 사용하여 위해 @SpringBootTest와 @Autowired constructor를 지정한다.


프로젝트에서 굳이 Kotlin으로 바꿔할 이유가 없다면,, 안바꿔도 되지 않을까? 그래도 Kotlin으로 작성했을 때 코드 생산성이 증가하는건 인정!

profile
'개발'은 '예술'이고 '서비스'는 '작품'이다

0개의 댓글