인프런스터디 5일차_클린코드 시작하기

nakyeonko3·2024년 2월 23일
0
post-thumbnail

<클린 코드>에 대한 감각을 익히기 위해서는 어떤 코드가 좋은 코드이고, 어떤 코드가 좋지 않은 코드인지 이론적인 배경을 학습하는 것도 중요할 뿐 아니라, 다양한 코드를 읽어 보며 어떤 부분이 읽기 쉬웠는지, 어떤 부분이 읽기 어려웠는지, 읽기 어려운 부분은 어떻게 고치면 좋을지 경험해보는 과정이 필요합니다.

레이어드 아키텍처 Layered Architecture


어플리케이션을 각 계층 단위로 나눠 정리하는 방식이다.
mvc 모델은 상단인 view에서 ui를 처리하고 controller에 넘겨주고,

mvc 아키텍처도 계층이 적지만 레이어드 아키텍처라고 할 수 있다.
controller 에서 데이터를 ui애서 넘겨준 데이터를 처리하고
변형 시키고 해당 데이터를 model에 넘겨줘서 model에서 해당 데이터를 저장한다.

` 웹어플리케이션은 스프링서버 , dbms, 프론트(리액트)로 구성되어 있다고 생각해보자. dbms가 데이터 저장소 역할을 하고, react가 html페이지 같은 사용자 인터페이스를 관리하는 역할을 하고, tomcat이 사용자에게 서비스를 제공하고 사용자에 대한 응답 처리를 하는 역할을한다. 이들도 계층 단위로 나눠진 레이어드 아키텍처라고 할 수 있다.

레이어드 아키텍처를 사용하는 이유는 코드의 복잡성을 높이고 가독성을 올리기 위해서이다.
각 계층의 레이어는 수정하거나 재사용하기 쉽다.

각 계층 단위로 추상화가 이루어지므로 해당 어플리케이션을 개발하는 당사자들과 합의를 하고 계층을 추상화시키는 것이 좋다.

controller, service, repository

강의에서는 controller, service, repository로 계층을 나눴는데 각 계층의 역할은 다음과 같다.

  • controller
    - api 진입 지점, 엔드포인트
    - 요청의 URL을 각 메서드에 매핑하는 기능을 한다.
    - HTTP BODY를 객체로 역직렬화(Deserialization)시킨다.

  • service
    - 데이터를 저장하고 유효성을 검증하고 데이터를 생성, 표시, 저장하는 비즈니스 로직을 수행하는 계층이다.
    - 내가 작성하는 코드에서는 유저의 데이터를 삭제시키거나 유저의 데이터를 변경하기 전에 해당 유저 데이터가 존재하는 지 확인하는 역할을 수행했다.
    - 참고 :object oriented - What really is the "business logic"? - Software Engineering Stack Exchange

  • repository
    - DB와 연결되는 지점
    - sql 쿼리를 날려 DBMS로 부터 데이터를 가져온다.

과제#5 클린코드 실습하기


이번 과제는 제시된 코드를 읽어보며, 코드를 더 좋은 코드로 고쳐나가는 과정입니다. 구글에 “클린 코드” 혹은 “클린 코드 정리”를 키워드로 검색해보면, 이론적인 배경을 충분히 찾아보실 수 있습니다. 🙂 그러한 내용들을 보며 제시된 코드를 더 좋은 코드로 바꿔보세요! (코드를 바꿀 때 왜 바뀐 코드가 더 좋은 코드인지 다른 사람에게 설명하신다고 생각해보시면 더욱 좋습니다.)

풀이과정

전체 소스코드 링크

첫번째 리펙토링

오늘 배운 레이어드 아키텍처를 이용해서 코드를 리펙토링 해보았다.

각 계층은 다음과 같다.

  • view
    - 사용자 입력을 받는다.
    - 숫자를 1~6 사이 숫자 1개를 사용자 입력을 받는다.
    - 해당 사용자 입력을 controller에 전달한다.

  • controller
    - 사용자 입력을 service에 전달한다.

  • service
    - 유효성 검증
    - 사용자가 1~6 사이 숫자가 아닌 값을 입력 했을 때 IllegalArgumentException 에러를 던진다.
    - 사용자가 입력한 숫자 만큼 주사위를 던지고, 각 숫자가 몇번 나왔는지를 r1 ~r6에 각각 저장한다.
    - Math.random을 통해 1~6 사이 숫자를 변수 b에 저장한다.
    - b 값이 1이면 r1, b값이 2이면 r2, b값이 2이면 r3, b값이 2이면 r4, b값이 2이면 r5 .. 각각 저장한다.

프로젝트 파일의 폴더 구조를 이렇게 생겼다.

실행 클래스

import com.group.Diceapp.view.DiceView;
public class DiceAppApplication {
    public static void main(String[] args) {
        new DiceView().diceRollResultView();
    }
}

view 클래스

public class DiceView {
    public void diceRollResultView(){
        List<Integer> diceRollResults =  new DiceController().getDiceNumber();
        for(int dicRollResult : diceRollResults){
            System.out.printf("%d은 %d번 나왔습니다, \n", dicRollResult+1, dicRollResult);
        }
    }
}

controller 클래스

public class DiceController {
    public List<Integer> getDiceNumber(){
        System.out.println("숫자를 입력하세요");
        Scanner scanner = new Scanner(System.in);
        int userInputNumer = scanner.nextInt();
        scanner.close();

        return new DiceService().getDiceRollResults(userInputNumer);
    }
}

service 클래스

public class DiceService {
    public List<Integer> getDiceRollResults(int userInputNumer){
     if (userInputNumer < 1 || userInputNumer > 6) {
            throw new IllegalArgumentException();
        }

        int r1 = 0, r2 = 0, r3 = 0, r4 = 0, r5 = 0, r6 = 0;
        for (int i = 0; i < userInputNumer; i++) {
            double b = Math.random() * 6;
            if (b >= 0 && b < 1) {
                r1++;
            } else if (b >= 1 && b < 2) {
                r2++;
            } else if (b >= 2 && b < 3) {
                r3++;
            } else if (b >= 3 && b < 4) {
                r4++;
            } else if (b >= 4 && b < 5) {
                r5++;
            } else if (b >= 5 && b < 6) {
                r6++;
            }
        }
        return Arrays.asList(r1, r2, r3, r4, r5, r6);
    }
}

리펙토링 결과 💩

각 계층에 대한 설명이 없으면 코드를 이해하기가 어렵다,
오히려 각 코드들을 나눠 두면서 코드를 읽고 이해하기가 어렵다.
차라리 적어도 한 파일 안에 모든 코드가 있었던 기존 코드가 더 이해하기가 쉬운 것 같다.
레이어드 아키텍처는 각 계층 레이어를 재사용하거나 수정하기 쉬운데 내코드는 더 복잡하고 재사용하기도 어려워졌다.
다시 해보자

일단 실행은 잘된다.

🤖 gpt야 리펙토링 해줘

gpt한테 내가 리펙토링 하기 전의 코드를 넣어서 리펙토링을 해달라고 했다
그 결과다.

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        System.out.println("숫자를 입력하세요:");
        Scanner scanner = new Scanner(System.in);
        int numberOfRolls = scanner.nextInt();

        int[] rollCounts = new int[6];

        for (int i = 0; i < numberOfRolls; i++) {
            int randomNumber = (int) (Math.random() * 6) + 1;
            rollCounts[randomNumber - 1]++;
        }

        for (int i = 0; i < rollCounts.length; i++) {
            System.out.printf("%d은(는) %d번 나왔습니다.\n", i + 1, rollCounts[i]);
        }
    }
}

🤖 주요 변경사항은 다음과 같습니다:

  1. 사용자에게 숫자를 입력하라는 메시지를 좀 더 명확하게 작성했습니다.
  2. int anumberOfRolls 로 변경하여 의미를 명확하게 했습니다.
  3. 불필요한 변수들을 제거하고, rollCounts 라는 배열을 사용하여 주사위가 각 숫자에 대해 나온 횟수를 저장했습니다.
  4. 주사위의 눈을 1부터 6까지의 정수로 표현했습니다. 따라서 (int) (Math.random() * 6) + 1 을 사용하여 1부터 6 사이의 난수를 생성했습니다.
  5. 출력부분을 루프를 통해 출력하도록 변경하여 중복을 줄였습니다.
  6. 주석을 추가하여 코드를 이해하기 쉽도록 했습니다.

gpt가 알려준 내용을 바탕으로 service 부분 코드 다시 리펙토링

일단 service 코드 부분만 다시 수정했다.
바뀐 부분은
1. r1 ~ r6 변수들을 rollCounts 리스트 변수로 바꾸었다.
2. b 변수를 randomNumber로 변수명을 바꾸었다.
3. else if 문을 제거하고 rollCounts리스트에 주사위를 돌린 결과를 저장하게 바꾸었다.

    public int[] getDiceRollResults(int userInputNumer){

        if (userInputNumer < 1 || userInputNumer >6) {
            throw new IllegalArgumentException();
        }

        int[] rollCounts = new int[6];
        for (int i = 0; i < userInputNumer; i++) {
             int randomNumber = (int) (Math.random() * 6) + 1;
            rollCounts[randomNumber + 1]++;
        }
        
        return rollCounts;
    }

그외 공부한 내용 정리


스프링 네이밍 컨벤션

아래 링크를 바탕으로 클래스, 메서드의 네이밍을 개선 해보도록하자
나만의 코딩컨벤션 작성하기(Spring, Java, Naming, 구조, 코드 작성법 등)

Repository에서 jdbcTemplate를 가져올 수는 없을까?

Controller에서 jdbc template를 어떻게 가져온 걸까?

Spring 에서 다중 데이터베이스 연결하기

해당 테이블이 없습니다. 'fruitshop.user' doesn't exist
[MyBatis] MyBatis란? 개념 및 데이터구조 :: 히진쓰의 서버사이드 기술 블로그

  : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.jdbc.BadSqlGrammarException: PreparedStatementCallback; bad SQL grammar [SELECT * FROM USER WHERE id = ?]; nested exception is java.sql.SQLSyntaxErrorException: Table 'fruitshop.user' doesn't exist] with root cause

java.sql.SQLSyntaxErrorException: Table 'fruitshop.user' doesn't exist
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:120) ~[mysql-connector-j-8.0.31.jar:8.0.31]

회고

잘 모르는 것을 무작정 검색하는 건 별도움이 안되는 것 같다.

클린 코드의 목적은 해당 코드를 더 알기 쉽게 바꾸고 복잡성을 줄여 코드 확장과 유지보수를 쉽게 하기 위해서이다.

이번 문제를 풀 때 클린한 코드를 작성하기 보다는 오늘 배운 내용인 레이어드 아키텍처부분을 어떻게든 적용하려고 레이어드 아키텍처 관련 내용만 구글에 검색하고 과제도 최대한 계층구조로 나눠도록 만들었다.

다시 클린코드 내용을 공부하고 다시 글을 수정 해서 올리도록 해야겠다.

참고


What is layered architecture and when and why should you use layered architecture when designing a system? : r/SoftwareEngineering

profile
웹개발자를 지망하고 있는 대학생, 진순파

0개의 댓글