[Spring] 프로젝트 리팩토링 1
구글 솔루션 챌린지에 제출한 프로젝트이다.
이 프로젝트는 바코드를 찍으면 해당 상품의 분리수거 방법을 알려주는 어플이다.
혼자 데이터베이스부터 배포까지 하다보니 시간이 부족했어서 코드의 부족함도 눈에 너무 보여서 리팩토링을 진행하게 되었다.
리팩토링 첫 번째 포스팅은 User 관련 코드이다.
1. Entity
import lombok.*;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name="User")
@AllArgsConstructor
@Builder
public class User extends BaseTimeEntity {
@Id
@Column(name="user_id", unique = true)
private String id; // 아이디
@Column(name = "user_pw", unique = true)
private String password; // 비밀번호
@Column(name = "user_checkpw")
private String checkpassword; // 비밀번호 확인
@Column(name = "user_nickname", unique = true, length = 20)
private String nickname; // 닉네임
private String date; // 분리수거 버리는 날 지정
}
2. UserDto
import com.barcode.solution_challenge_7_back.domain.User;
import lombok.*;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserDto {
private String id; // 유저 아이디
private String password; // 유저 비밀번호
private String checkpassword; // 비밀번호 확인
private String nickname; // 유저 닉네임
private String date; // 쓰레기 버리는 날
public User toEntity(){
return User.builder()
.id(id)
.password(password)
.checkpassword(checkpassword)
.nickname(nickname)
.date(date)
.build();
}
}
3. LoginRequest
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@AllArgsConstructor
@Getter
public final class LoginRequest { // 로그인 요정
private String id;
private String password;
}
4. UserDuplicatedException (중복 Exception)
public class UserDuplicatedException extends RuntimeException {
public UserDuplicatedException(String message) {
super(message);
}
}
5. UserPasswordMismatchException (비밀번호 오류 Exception)
public class UserPasswordMismatchException extends Exception {
public UserPasswordMismatchException(String message) {
super(message);
}
}
6. UserRepository
import com.barcode.solution_challenge_7_back.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<User, String> {
Optional<User> findById(String id);
Optional<User> findByNickname(String nickname);
}
7. UserService
import com.barcode.solution_challenge_7_back.domain.User;
import com.barcode.solution_challenge_7_back.domain.dto.UserDto;
import com.barcode.solution_challenge_7_back.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
@Service
@Transactional
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public void save(UserDto userDto) {
userDto.setPassword(passwordEncoder.encode(userDto.getPassword()));
userRepository.save(userDto.toEntity());
}
public String login(String id, String password){ // 로그인
Optional<User> user = userRepository.findById(id);
if(user.isPresent() && passwordEncoder.matches(password, user.get().getPassword())){
return "true";
}
else{
return "false";
}
}
public boolean checkLogin(String id, String password) {
Optional<User> user = userRepository.findById(id);
if(!user.isPresent()){
return false;
}
return passwordEncoder.matches(password, user.get().getPassword());
}
public boolean checkDuplicateNickname(String username) {
return userRepository.findByNickname(username).isPresent();
}
public boolean checkDuplicateId(String id){
return userRepository.findById(id).isPresent();
}
}
8. Controller
import com.barcode.solution_challenge_7_back.domain.User;
import com.barcode.solution_challenge_7_back.domain.dto.UserDto;
import com.barcode.solution_challenge_7_back.domain.request.LoginRequest;
import com.barcode.solution_challenge_7_back.exception.UserPasswordMismatchException;
import com.barcode.solution_challenge_7_back.repository.UserRepository;
import com.barcode.solution_challenge_7_back.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Optional;
@RestController
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
private final UserRepository userRepository;
@PostMapping("/users/new-user") // 회원가입
public ResponseEntity<String> join(@RequestBody UserDto userDto) throws UserPasswordMismatchException {
if (!userDto.getPassword().equals(userDto.getCheckpassword())) {
throw new UserPasswordMismatchException("패스워드가 일치하지 않습니다.");
}
userService.save(userDto);
return ResponseEntity.ok("join success");
}
@PostMapping("/login") // 로그인
public ResponseEntity<Boolean> login(@RequestBody LoginRequest request) {
boolean isAuthenticated = userService.checkLogin(request.getId(), request.getPassword());
return isAuthenticated ? ResponseEntity.ok(true) : ResponseEntity.badRequest().body(false);
}
@GetMapping("/checkDuplicateId/{id}") // 아이디 중복 확인
public boolean checkDuplicateId(@PathVariable String id) {
return userService.checkDuplicateId(id);
} // 중복이면 true, 중복 아니면 false
@GetMapping("/checkDuplicateNickname/{nickname}")
public boolean checkDuplicateNickname(@PathVariable String nickname) {
return userService.checkDuplicateNickname(nickname);
}
@GetMapping("/user/{userId}/day")
public ResponseEntity<String> getUserCustomDay(@PathVariable String userId) {
Optional<User> optionalUser = userRepository.findById(userId);
if (optionalUser.isPresent()) {
User user = optionalUser.get();
String date = user.getDate();
if (date != null && !date.isEmpty()) {
return ResponseEntity.ok(date);
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body("Custom day not found for user " + userId);
}
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body("User not found with id " + userId);
}
}
}
9. SecurityConfig
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().disable()
.csrf().disable()
.httpBasic().disable();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
코드 전체는
GitHub - GDSC-SKHU/Solution-Challenge-7team-Backend: 솔루션챌린지 7팀 백엔드
솔루션챌린지 7팀 백엔드. Contribute to GDSC-SKHU/Solution-Challenge-7team-Backend development by creating an account on GitHub.
github.com
에서 확인할 수 있다.
리팩토링한 코드는 다음과 같다.
1. Entity
import lombok.*;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name="User")
public class User {
@Id
@Column(name="user_id", unique = true)
private String id; // 아이디
@Column(name = "user_pw", unique = true)
private String password; // 비밀번호
@Column(name = "user_nickname", unique = true, length = 20)
private String nickname; // 닉네임
private String date; // 분리수거 버리는 날 지정
@Builder
public User(String id, String password, String nickname, String date){
this.id = id;
this.password = password;
this.nickname = nickname;
this.date = date;
}
}
@Builder를 클래스 위에 선언한다기 보다는, 클래스 내부에 직접 정의한다든가
@NoArgsConstructor는 (access = AccessLevel.PROTECTED)로 설정해둔다든가,
조건에 맞게 정리해줬다.
2. UserDto
import com.barcode.solution_challenge_7_back.domain.User;
import lombok.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class UserDto {
@NotNull(message = "아이디는 필수 입력 값입니다.")
private String id; // 유저 아이디
@NotBlank(message = "비밀번호는 필수 입력 값입니다.")
@Pattern(regexp = "(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,16}", message = "비밀번호는 8~16자 영문 대 소문자, 숫자, 특수문자를 사용하세요.")
private String password; // 유저 비밀번호
@NotBlank
@Pattern(regexp = "^[가-힣a-zA-Z]{2,10}$", message = "닉네임 형식에 맞지 않습니다.")
private String nickname; // 유저 닉네임
@NotBlank(message = "쓰레기 버리는 날은 필수 입력 값입니다.")
private String date; // 쓰레기 버리는 날
@Builder
public UserDto(String id, String password, String nickname, String date){
this.id = id;
this.password = password;
this.nickname = nickname;
this.date = date;
}
}
validation을 사용하여 메세지를 작성해줬다.
3. ApiResponseDto
import com.barcode.solution_challenge_7_back.status.ErrorStatus;
import com.barcode.solution_challenge_7_back.status.SuccessStatus;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public class ApiResponseDto<T> {
private final int code;
private final String message;
private T data;
public static ApiResponseDto success(SuccessStatus successStatus) {
return new ApiResponseDto<>(successStatus.getHttpStatus().value(), successStatus.getMessage());
}
public static <T> ApiResponseDto<T> success(SuccessStatus successStatus, T data) {
return new ApiResponseDto<T>(successStatus.getHttpStatus().value(), successStatus.getMessage(), data);
}
public static ApiResponseDto error(ErrorStatus errorStatus) {
return new ApiResponseDto<>(errorStatus.getHttpStatus().value(), errorStatus.getMessage());
}
}
클라이언트에게 일관된 응답을 보내기 위해 ApiResponseDto를 사용했다.
success일 때와 error일 때 모두를 만들어줬다.
4. LoginRequest
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public final class LoginRequest { // 로그인 요정
private String id;
private String password;
}
5. SignupRequest
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class SignupRequest { // 회원가입 요청
private String id;
private String password;
private String nickname;
private String date;
}
회원가입을 userDto로 받지 않고 SignupRequest를 사용했다.
6. LoginResponse
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public class LoginResponse {
private String id;
public static LoginResponse of(String id){
return new LoginResponse(id);
}
}
로그인 응답을 생성해줬다. id만 리턴할 수 있도록 매개변수는 id만 받아줬다.
7. SignupReponse
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class SignupResponse {
private String userId;
private String nickname;
private String date;
public static SignupResponse of(String userId, String nickname, String date) {
return new SignupResponse(userId, nickname, date);
}
}
회원가입을 했을 때는 아이디와 닉네임, 분리수거 날짜를 리턴하도록 Response를 생성했다.
8. DataResponse
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public class DateResponse {
private String date;
public static DateResponse of(String date){
return new DateResponse(date);
}
}
회원가입 시 지정했던 날짜를 리턴하는 Response를 생성했다.
9. UserService
import com.barcode.solution_challenge_7_back.domain.User;
import com.barcode.solution_challenge_7_back.domain.dto.UserDto;
import com.barcode.solution_challenge_7_back.domain.request.SignupRequest;
import com.barcode.solution_challenge_7_back.domain.response.SignupResponse;
import com.barcode.solution_challenge_7_back.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
@Transactional
public SignupResponse save(SignupRequest request){
User user = User.builder()
.id(request.getId())
.password(passwordEncoder.encode(request.getPassword()))
.nickname(request.getNickname())
.date(request.getDate())
.build();
userRepository.save(user);
return SignupResponse.from(user.getId(), user.getNickname(), user.getDate());
}
public boolean checkLogin(String id, String password) {
Optional<User> user = userRepository.findById(id);
if(!user.isPresent()){
return false;
}
return passwordEncoder.matches(password, user.get().getPassword());
}
public boolean checkDuplicateNickname(String username) {
return !userRepository.findByNickname(username).isPresent();
}
public boolean checkDuplicateId(String id){
return !userRepository.findById(id).isPresent();
}
}
- 회원가입
회원가입 시 SingupResponse를 리턴하도록 로직을 수정했다.
빌더 패턴을 사용하여 아이디, 비밀번호, 닉네임, 날짜를 생성해줬다.
레포지토리에 저장한 후, SingupResponse에 만들었던 from 메소드를 리턴하도록 한다.
- 로그인 확인
레포지토리에서 아이디를 찾아와서, 유저가 존재하지 않으면 false를 리턴하고 유저가 존재한다면 비밀번호가 맞는지 확인하여 boolean 값을 리턴한다.
- 닉네임 중복 확인
레포지토리에서 닉네임을 가져온 후, 존재한다면 false(중복 x를 의미)를 리턴한다.
- 아이디 중복 확인
레포지토리에서 아이디를 가져온 후, 존재한다면 false(중복 x를 의미)를 리턴한다.
10. SuccesStatus
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
@Getter
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public enum SuccessStatus {
SIGNUP_SUCCESS(HttpStatus.CREATED, "회원가입이 완료되었습니다."),
CREATE_ID_SUCCESS(HttpStatus.OK, "사용 가능한 아이디입니다."),
CREATE_NICKNAME_SUCCESS(HttpStatus.OK, "사용 가능한 닉네임입니다."),
LOGIN_SUCCESS(HttpStatus.OK, "로그인 성공!"),
CERTIFICATION_SUCCESS(HttpStatus.OK, "유저 인증 성공"),
BRING_DATE_SUCCESS(HttpStatus.OK, "사용자 지정 요일 가져오기 성공")
;
private final HttpStatus httpStatus;
private final String message;
}
성공적으로 요청이 완료되었을 때의 메세지를 저장하는 SuccessStatus이다.
11. ErrorStatus
@Getter
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public enum ErrorStatus {
/*
BAD_REQUEST
*/
VALIDATION_EXCEPTION(HttpStatus.BAD_REQUEST, "잘못된 요청입니다."),
USER_CERTIFICATION_FAILED(HttpStatus.UNAUTHORIZED, "해당 아이디나 비밀번호를 가진 유저가 존재하지 않습니다."),
USER_NOT_JOIN(HttpStatus.FORBIDDEN, "해당 사용자가 존재하지 않습니다."),
/*
CONFLICT
*/
CONFLICT_ID_EXCEPTION(HttpStatus.CONFLICT, "이미 등록된 id입니다."),
CONFLICT_NICKNAME_EXCEPTION(HttpStatus.CONFLICT, "이미 등록된 닉네임입니다."),
/*
SERVER_ERROR
*/
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "예상치 못한 서버 에러가 발생했습니다."),
BAD_GATEWAY_EXCEPTION(HttpStatus.BAD_GATEWAY, "일시적인 에러가 발생하였습니다.\n잠시 후 다시 시도해주세요!"),
;
private final HttpStatus httpStatus;
private final String message;
}
오류가 발생했을 때의 메세지를 나타내는 ErrorStatus 이다.
12. Controller
import com.barcode.solution_challenge_7_back.domain.User;
import com.barcode.solution_challenge_7_back.domain.dto.ApiResponseDto;
import com.barcode.solution_challenge_7_back.domain.dto.UserDto;
import com.barcode.solution_challenge_7_back.domain.request.LoginRequest;
import com.barcode.solution_challenge_7_back.domain.response.DateResponse;
import com.barcode.solution_challenge_7_back.domain.response.LoginResponse;
import com.barcode.solution_challenge_7_back.domain.response.SignupResponse;
import com.barcode.solution_challenge_7_back.repository.UserRepository;
import com.barcode.solution_challenge_7_back.service.UserService;
import com.barcode.solution_challenge_7_back.status.ErrorStatus;
import com.barcode.solution_challenge_7_back.status.SuccessStatus;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponses;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.Optional;
@RestController
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
private final UserRepository userRepository;
@ApiOperation(value = "회원가입", notes = "새로운 사용자를 회원으로 등록합니다.")
@ApiResponses(value = {
@io.swagger.annotations.ApiResponse(code = 200, message = "회원가입 성공"),
@io.swagger.annotations.ApiResponse(code = 400, message = "잘못된 요청 형식"),
@io.swagger.annotations.ApiResponse(code = 500, message = "서버 오류")
})
@PostMapping("/users/new-user") // 회원가입
public ApiResponseDto<SignupResponse> join(@RequestBody SignupRequest request) {
try {
return ApiResponseDto.success(SuccessStatus.SIGNUP_SUCCESS, userService.save(request)); // 회원가입 성공
} catch (Exception e) { // 그 밖의 예외 발생시
return ApiResponseDto.error(ErrorStatus.INTERNAL_SERVER_ERROR);
}
}
@ApiOperation(value = "로그인", notes = "아이디, 비밀번호로 로그인")
@ApiResponses(value = {
@io.swagger.annotations.ApiResponse(code = 200, message = "로그인 성공"),
@io.swagger.annotations.ApiResponse(code = 400, message = "잘못된 요청 형식"),
@io.swagger.annotations.ApiResponse(code = 500, message = "서버 오류")
})
@PostMapping("/login") // 로그인
public ApiResponseDto<LoginResponse> login(@RequestBody LoginRequest request) {
boolean isAuthenticated = userService.checkLogin(request.getId(), request.getPassword());
return isAuthenticated ? ApiResponseDto.success(SuccessStatus.LOGIN_SUCCESS, LoginResponse.of(request.getId())) : ApiResponseDto.error(ErrorStatus.USER_CERTIFICATION_FAILED);
}
@ApiOperation(value = "아이디 중복 확인", notes = "해당 id가 이미 존재한다면 true, 존재하지 않다면 false를 반환합니다.")
@ApiImplicitParam(name = "id", value = "사용자가 생성한 id")
@GetMapping("/checkDuplicateId/{userId}") // 아이디 중복 확인
public ApiResponseDto<String> checkDuplicateId(@PathVariable String userId) {
return userService.checkDuplicateId(userId) ? ApiResponseDto.success(SuccessStatus.CREATE_ID_SUCCESS, userId)
: ApiResponseDto.error(ErrorStatus.CONFLICT_ID_EXCEPTION, userId);
}
@ApiOperation(value = "닉네임 중복 확인", notes = "해당 닉네임이 이미 존재한다면 true, 존재하지 않다면 false를 반환합니다.")
@ApiImplicitParam(name = "nickname", value = "사용자가 생성한 닉네임")
@GetMapping("/checkDuplicateNickname/{nickname}")
public ApiResponseDto<String> checkDuplicateNickname(@PathVariable String nickname) {
return userService.checkDuplicateNickname(nickname) ? ApiResponseDto.success(SuccessStatus.CREATE_NICKNAME_SUCCESS, nickname)
: ApiResponseDto.error(ErrorStatus.CONFLICT_NICKNAME_EXCEPTION, nickname);
}
@ApiOperation(value = "유저 날짜 가져오기", notes = "회원가입할 때 저장한 날짜를 가져옵니다.")
@ApiImplicitParam(name = "userId", value = "사용자의 userId")
@GetMapping("/user/{userId}/day")
public ApiResponseDto<DateResponse> getUserCustomDay(@PathVariable String userId) {
Optional<User> optionalUser = userRepository.findById(userId);
if (optionalUser.isPresent()) {
User user = optionalUser.get();
return ApiResponseDto.success(SuccessStatus.BRING_DATE_SUCCESS, DateResponse.of(user.getDate()));
} else {
return ApiResponseDto.error(ErrorStatus.INTERNAL_SERVER_ERROR);
}
}
}
- 회원가입
SingupResponse를 응답으로 리턴한다. 회원가입 요청이 성공적으로 저장이 된다면 SIGNUP_SUCCESS 메세지를 리턴한다.
그 밖의 에러가 발생한다면 INTERNAL_SERVER_ERROR 메세지를 리턴한다.
- 로그인
서비스단의 checkLogin 로직을 활용하여 아이디와 비번을 확인하고 성공 시에는 LOGIN_SUCCESS 메세지와 로그인 한 아이디를 리턴하고, 실패 시에는 USER_CERTIFICATION_FAILED 메세지를 리턴한다.
- 중복 확인
[Spring] 중복 확인 Rest API 설계
아이디와 닉네임에 대해 중복을 확인할 수 있는 Rest API이다. 1. Entity @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Table(name="User") public class User { @Id @Column(name="user_id", unique = true) private String i
jiyoungmerong.tistory.com
블로그에 자세히 정리해뒀다.
- 유저 날짜 가져오기
레포지토리에서 아이디를 가져와 유저 존재 여부를 확인 후, 존재한다면 BRING_DATE_SUCCESS 메세지와 날짜를 리턴해준다.
존재하지 않다면 INTERNAL_SERVER_ERROR 메세지를 리턴한다.
13. SwaggerConfig
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket api(){
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build();
}
}
스웨거 사용을 위해 SwaggerConfig를 작성했다.
UserRepository와 SecurityConfig는 동일하다!
코드 전체는
GitHub - jiyoungmerong/Solution-Challenge-7team-Backend: 솔루션챌린지 7팀 백엔드
솔루션챌린지 7팀 백엔드. Contribute to jiyoungmerong/Solution-Challenge-7team-Backend development by creating an account on GitHub.
github.com
링크를 통해 확인할 수 있다.
코드를 리팩토링 하면서 나쁜 코드가 무엇인지를 내 코드를 통해 알게 되었다.
어노테이션의 개념에 대해서도 잘 모르면서 남용했단 것 같다.
앞으로는 왜 사용되는지 꼭 생각하며 코드를 작성해야겠다!