Spring boot

Bank App 만들기 - 중간 리팩토링

햄발자 2024. 9. 27. 15:20

리팩토링이란?(Refactoring)

소프트웨어의 외부 동작을 변경하지 않으면서 내부 구조를 체계적으로 개선하는 과정을 말합니다.

이 과정은 코드의 가독성을 높이고, 유지보수를 용이하게 하며, 오류 발견 및 수정을 용이하게 하는 것을 목표로 합니다.

리팩토링은 소프트웨어 개발의 중요한 부분으로, 코드의 품질을 지속적으로 향상시키기 위해 필요합니다.

 

리팩토링의 목적

  • 가독성 향상
    코드를 더 이해하기 쉽게 만들어 다른 개발자가 코드를 빠르게 이해하고 수정할 수 있도록 합니다.


  • 유지보수성 개선
    코드의 구조를 개선하여 나중에 버그를 수정하거나 새로운 기능을 추가할 때 필요한 노력을 줄입니다.


  • 성능 최적화
    비효율적인 코드를 개선하여 애플리케이션의 실행 성능을 향상시킬 수 있습니다.


  • 재사용성 증가
    코드의 모듈성을 높여 다른 프로젝트나 다른 부분에서 코드를 재사용하기 쉽게 만듭니다.


  • 버그 발견
    코드를 정리하고 개선하는 과정에서 숨어 있던 버그를 발견하고 수정할 기회를 얻습니다.

리팩토링의 원칙

  1. 외부 동작 유지
    리팩토링은 코드의 외부 동작을 변경해서는 안 됩니다.

    사용자 입장에서는 리팩토링 전후에 애플리케이션이 동일하게 동작해야 합니다.

  2. 작은 단계로 진행
    대규모 변경보다는 작은 변경을 반복적으로 적용하여 점진적으로 코드를 개선합니다.


  3. 테스팅
    리팩토링 과정에서는 지속적으로 테스트를 수행하여 리팩토링이 외부 동작에 영향을 미치지 않도록 합니다.


  4. 지속적인 개선
    리팩토링은 한 번에 끝나는 과정이 아니라, 지속적으로 코드를 개선해 나가는 과정입니다.

리팩토링의 예

  • 변수 이름 변경
    더 의미 있는 변수 이름을 사용하여 코드의 의도를 명확하게 합니다.


  • 함수 분리
    크고 복잡한 함수를 더 작고 관리하기 쉬운 여러 함수로 분리합니다.


  • 중복 코드 제거
    반복되는 코드를 찾아내어 함수로 추출하거나 다른 구조로 재구성합니다.


  • 디자인 패턴 적용
    코드 구조를 개선하기 위해 적절한 디자인 패턴을 적용합니다.
  • 조건문 간소화
    복잡한 조건문을 더 단순하거나 명확한 로직으로 재작성합니다.

 

Define.java 클래스 만들기 (상수로 만들자)

Define.java

더보기
닫기
더보기

Define.java

package com.tenco.bank.utils;


public class Define {
	//  상수
	public static final String PRINCIPAL = "principal";
	
	// 이미지 관련
	public static final String UPLOAD_FILE_DERECTORY = "C:\\work_spring\\upload/";
	public static final int MAX_FILE_SIZE = 1024 * 1024 * 20; // 20MB

	//  Account
	public static final String EXIST_ACCOUNT = "이미 계좌가 존재합니다.";
	public static final String NOT_EXIST_ACCOUNT = "존재하는 계좌가 없습니다.";
	public static final String FAIL_TO_CREATE_ACCOUNT = "계좌 생성이 실패하였습니다.";
	public static final String FAIL_ACCOUNT_PASSWROD = "계좌 비밀번호가 틀렸습니다.";
	public static final String LACK_Of_BALANCE = "출금 잔액이 부족 합니다.";
	public static final String NOT_ACCOUNT_OWNER = "계좌 소유자가 아닙니다.";
	

	//  User
	public static final String ENTER_YOUR_LOGIN = "로그인 먼저 해주세요.";
	public static final String ENTER_YOUR_USERNAME = "username을 입력해 주세요.";
	public static final String ENTER_YOUR_FULLNAME = "fullname을 입력해 주세요.";
	public static final String ENTER_YOUR_ACCOUNT_NUMBER = "계좌번호를 입력해 주세요.";
	public static final String ENTER_YOUR_PASSWORD = "패스워드를 입력해 주세요.";
	public static final String ENTER_YOUR_BALANCE = "금액을 입력해 주세요.";
	public static final String D_BALANCE_VALUE ="입금 금액이 0원 이하 일 수 없습니다.";
	public static final String W_BALANCE_VALUE ="출금 금액이 0원 이하 일 수 없습니다.";
	
	// etc 
	public static final String FAIL_TO_CREATE_USER = "회원가입 실패.";
	public static final String NOT_AN_AUTHENTICATED_USER = "인증된 사용자가 아닙니다.";
	public static final String INVALID_INPUT = "잘못된 입력입니다.";
	public static final String UNKNOWN = "알 수 없는 동작입니다";
	public static final String FAILED_PROCESSING = "정상 처리 되지 않았습니다.";
}

 

static 변수는 클래스 당 하나만 생성되며, 모든 인스턴스가 이 변수를 공유합니다.

따라서 같은 값을 반복적으로 사용할 때 메모리 사용을 최소화할 수 있습니다.

상수 값은 애플리케이션 전반에 걸쳐 변경되지 않고 반복적으로 사용되므로 final static을 사용하여 정의하는 것이 메모리를 효율적으로 사용하는 방법입니다.

 

UserController.java

더보기
닫기
더보기

UserController.java

package com.tenco.bank.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import com.tenco.bank.dto.SignInDTO;
import com.tenco.bank.dto.SignUpDTO;
import com.tenco.bank.handler.exception.DataDeliveryException;
import com.tenco.bank.repository.model.User;
import com.tenco.bank.service.UserService;
import com.tenco.bank.utils.Define;

import jakarta.servlet.http.HttpSession;

@Controller // IoC에 대상(싱글톤 패턴으로 관리됨) 
@RequestMapping("/user") // 대문 처리 
public class UserController {
	
	
	private UserService userService;
	
	private final HttpSession session; // 세션  // final은 단 한번 초기화 해야한다.
	
	// DI 처리
	@Autowired // 노란색 경고는 사용할 필요는 없음 -- 가독성을 위해서 선언해도 됨
	public UserController(UserService service, HttpSession session) {
		this.userService = service;
		this.session = session; // 메모리에 띄울려면 생성자에서 선언
	}

	/**
	 * 회원 가입 페이지 요청 
	 * 주소 설계 : http://localhost:8080/user/sign-up
	 * @return signUp.jsp 
	 */
	@GetMapping("/sign-up")
	public String signUpPage() {
		return "user/signUp";
	}
	
	/**
	 * 회원 가입 로직 처리 요청
	 * 주소 설계 : http://localhost:8080/user/sign-up
	 * @param dto
	 * @return
	 */
	@PostMapping("/sign-up")
	public String signUpProc(SignUpDTO dto) {
		// controller 에서 일반적이 코드 작업 
		// 1. 인증검사 (여기서는 인증검사 불 필요) 
		// 2. 유효성 검사 
		if(dto.getUsername() == null || dto.getUsername().isEmpty()) {
			throw new DataDeliveryException(Define.ENTER_YOUR_USERNAME, HttpStatus.BAD_REQUEST);
		}
		
		if(dto.getPassword() == null || dto.getPassword().isEmpty()) {
			throw new DataDeliveryException(Define.ENTER_YOUR_PASSWORD , HttpStatus.BAD_REQUEST);
		}
		
		if(dto.getFullname() == null || dto.getFullname().isEmpty()) {
			throw new DataDeliveryException(Define.ENTER_YOUR_FULLNAME, HttpStatus.BAD_REQUEST);
		}
		// 서비스 객체로 전달 
		userService.createUser(dto);
		return "redirect:/user/sign-in";
	}
	
	/**
	 * 로그인 화면 요청
	 * 주소설계 : http://localhost:8080/user/sign-in
	 * @return
	 */
	@GetMapping("/sign-in")
	public String signInPage() {
		// 인증검사 , 유효성 검사 로그인 페이지에서 할 필요가 없다.
		
		return "user/signIn";
	}
	
	/**
	 * 회원 로그인 요청 처리
	 * 주소설계 : http://localhost:8080/user/sign-in
	 * @return
	 */
	@PostMapping("/sign-in")
	public String signProc(SignInDTO dto) {
		
		// 1, 인증 검사 x
		// 2. 유효성 검사 O
		if(dto.getUsername() == null || dto.getUsername().isEmpty()) {
			throw new DataDeliveryException(Define.ENTER_YOUR_USERNAME, HttpStatus.BAD_REQUEST);
		} // 이름 검사
		
		if(dto.getPassword() == null || dto.getPassword().isEmpty()) {
			throw new DataDeliveryException(Define.ENTER_YOUR_PASSWORD, HttpStatus.BAD_REQUEST);
		} // 패스워드 검사
		
		// 서비스 호출
		User principal =  userService.readUser(dto); // DB에 조회된 데이터가 들어가고
		
		// 세션 메모리에 등록 처리
		session.setAttribute(Define.PRINCIPAL, principal); // User 값 세션에 등록완룐
		
		// 새로운 페이지로 이동 처리 
		// TODO - 계좌 목록 페이지 이동처리 예정
		return "redirect:/account/list";
	}
	
	/**
	 * 로그아웃 기능 추가
	 * 로그아웃 처리
	 * 코드 추가
	 * @return
	 */
	@GetMapping("/logout")
	public String logout() {
		
		session.invalidate(); // 로그아웃 됨 (세션 무효화 된다.)
		
		return "redirect:/user/sign-in";
	}
	
}

 

AccountController.java

더보기
닫기
더보기

AccountController.java

package com.tenco.bank.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import com.tenco.bank.dto.SaveDTO;
import com.tenco.bank.handler.exception.DataDeliveryException;
import com.tenco.bank.handler.exception.UnAuthorizedException;
import com.tenco.bank.repository.model.Account;
import com.tenco.bank.repository.model.User;
import com.tenco.bank.service.AccountService;
import com.tenco.bank.utils.Define;

import jakarta.servlet.http.HttpSession;

@Controller // IOC 대상 (싱글톤으로 관리)
@RequestMapping("/account")
public class AccountController {

	// 계좌 생성 화면 요청 - DI 처리
	private final HttpSession session;
	private final AccountService accountService;

	@Autowired
	public AccountController(HttpSession session, AccountService accountService) {
		this.session = session; // 의존 주입
		this.accountService = accountService; // 의존 주입
	}

	/**
	 * 계좌 생성 페이지 요청 주소설계 : http://localhost:8080/account/save :
	 * 
	 * @return
	 */
	@GetMapping("/save")
	public String savePage() {

		// 1. 인증 검사가 필요 (account 전체가 필요함)
		User principal = (User) session.getAttribute(Define.PRINCIPAL);
		if (principal == null) {
			throw new UnAuthorizedException(Define.NOT_AN_AUTHENTICATED_USER, HttpStatus.UNAUTHORIZED);
		}

		return "account/save";
	}

	/**
	 * 계좌 생성 기능 요청 
	 * 주소 설계 : http://localhost:8080/account/save
	 * @retrun : 추후 계좌 목록 페이지 이동 처리
	 */
	
	@PostMapping("/save")
	public String saveProc(SaveDTO dto) {
		// 1. form 데이터 추출 (파싱 전략) 
		// 2. 인증 검사 
		// 3. 유효성 검사 
		// 4. 서비스 호출 
		User principal = (User)session.getAttribute(Define.PRINCIPAL); 
		
		// 인증 검사
		if(principal == null) {
			throw new UnAuthorizedException(Define.NOT_AN_AUTHENTICATED_USER, HttpStatus.UNAUTHORIZED);
		}
		
		if(dto.getNumber() == null || dto.getNumber().isEmpty()) {
			throw new DataDeliveryException(Define.ENTER_YOUR_ACCOUNT_NUMBER, HttpStatus.BAD_REQUEST);
		}
		
		if(dto.getPassword() == null || dto.getPassword().isEmpty()) {
			throw new DataDeliveryException(Define.ENTER_YOUR_PASSWORD, HttpStatus.BAD_REQUEST);
		}
		
		if(dto.getBalance() == null || dto.getBalance() <= 0) {
			throw new DataDeliveryException(Define.ENTER_YOUR_BALANCE, HttpStatus.BAD_REQUEST);
		}
		
		// 서비스에 dto와 유저아이디를 보낸다.
		accountService.createAccount(dto, principal.getId());
		
		return "redirect:/index";
	}
	
	/**
	 * 계좌 목록 화면 요청
	 * 주소 설계 : http://localhost:8080/account/list, ..../
	 * @return list.jsp
	 */
	// 페이지 리턴해야 되서 string으로 짓는다.
	@GetMapping({"/list", "/"})
	public String listPage(Model model) {
		
		// 1. 인증검사
		User principal = (User) session.getAttribute(Define.PRINCIPAL); // 유저세션 가져옴
		if(principal == null) {
			// 로그인을 안한 상태
			throw new UnAuthorizedException(Define.NOT_AN_AUTHENTICATED_USER, HttpStatus.UNAUTHORIZED);
		}
		// 2. 유효성 검사
		
		// 3. 서비스 호출 (서비스컨트롤러 : 핵심기능 )
		List<Account> accountList = accountService.readAccountListByUserId(principal.getId());
		if(accountList.isEmpty()) {
			model.addAttribute("accountList", null);
		} else {
			model.addAttribute("accountList", accountList); // 모델에서 키,값을 던져버린다.
		}
	
		// JSP 데이터를 넣어 주는 방법
		return "account/list";
		
	}

}

 

GlobalControllerAdvice 코드 수정 (unAuthorizedException 메서드 수정)

GlobalControllerAdvice.java

더보기
닫기
더보기

GlobalControllerAdvice.java

	@ResponseBody
	@ExceptionHandler(UnAuthorizedException.class)
	public String unAuthorizedException(UnAuthorizedException e) {
		StringBuffer sb = new StringBuffer();
		sb.append(" <script>");
		sb.append(" alert('"+ e.getMessage()  +"');");
		sb.append(" location.href='/user/sign-in';");
		sb.append(" </script>");
		return sb.toString(); 
	}