문제는 다음과 같다.

 

 

 

풀이는 비교적 간단하다.

먼저, 이중for문을 돌면서 기준 단어와 대상 단어의 접두사 길이를 구한다.이후 새로 구한 길이가 더 클 경우에만 업데이트를 한다.

 

package baekjoon;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;

public class 비슷한단어_2179 {

    public static void main(String[] args) throws IOException {
        BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
        int N = Integer.parseInt(bf.readLine());

        char[][] inputs = new char[N][];

        for(int i=0;i<N;i++) {
            inputs[i] = bf.readLine().toCharArray();
        }

        int idx1 = -1, idx2 = -1, count = -1;

        for(int i=0;i<N-1;i++) {

            for(int j=i+1;j<N;j++) {
                if(inputs[i][0] != inputs[j][0]) continue;
                int cnt = 0;
                while(cnt<inputs[i].length && cnt < inputs[j].length && inputs[i][cnt] == inputs[j][cnt]) {
                    cnt++;
                }
                if(cnt > count) {
                    count = cnt;
                    idx1 = i; idx2 = j;
                }
            }
        }

        for(int i=0;i<inputs[idx1].length;i++) {
            System.out.print(inputs[idx1][i]);
        }
        System.out.println();
        for(int i=0;i<inputs[idx2].length;i++) {
            System.out.print(inputs[idx2][i]);
        }
    }
}

 

주인장은 요즘 리팩토링에 빠졌습니다.

아침에 일어나서 자기 전까지 하루종일 리팩토링만 하고 있는 것 같아요(거짓말입니다)

제 코드 및 다른 팀원들의 코드를 내맘대로 리뷰하는 중 여러 Handler를 사용하면 코드가 더 깔끔하고 유지보수가 편해질 것 같아 상의도 없이 만들고 PR날린 그것,

ErrorHandler, Validator

기존 코드를 봅시다

@Service
@RequiredArgsConstructor
public class BattleServiceImpl implements BattleService {

	//생략

	@Override
	public void registBattle(BattleInviteRequest battleInviteRequest, User user) {
    
    	//생략
    
    	if (battleInviteRequest.getOppositeUserId() == user.getId()) {
			throw new IllegalArgumentException("Not valid user");
		}
		if (battleInviteRequest.getTime() <= 0) {
			throw new RuntimeException("Wrong time setting");
		}

 

아...

진짜 아... 싶죠? 분명 던지는 에러들이 서버 상 오류가아닌 비지니스로직 적 이루어질 수 있는 에러인데, 저렇게 서비스단에 2~3줄 심지어 if문으로 메서드 안에 넣으면...... 나중에 혹시라도 수정하게되거나 로직을 변경해야 되는 일이 생겼을 시 굉장히 귀찮아지겠죠?ㅎㅎ....

 

여기서 제가 생각해낸 방법은

1. 체크해야되는 조건들을 하나의 파일로 묶는다.

2. 오류 결과들을 하나의 파일에 정리한다

3. 던진다!

 

의 과정입니다.

 

먼저, 저 말도안되는 Not a valid user과 Wrong time setting을 과감히 지우고, "ErrorCode"의 Enum을 만들었습니다.

@Getter
@AllArgsConstructor
public enum ErrorCode {

    INVALID_BATTLE_TIME("최소 10분부터 최대 60분까지 설정 가능합니다."),
    INVALID_USER("유저 설정이 유효하지 않습니다."),
    INVALID_BATTLE_STARTTIME("시작 시간은 최소 60분 후부터 가능합니다."),
    INVALID_OPINION_LENGTH("선택지는 최대 16자까지 작성 가능합니다."),
    DUPLICATED_BATTLE("이미 해당시간에 예정된 배틀이 존재합니다."),
    ENDED_BATTLE("응답 기간이 종료된 배틀입니다.");

    private final String message;
}

 

Wrong time setting보다 저렇게 친절하게 message를 적어준다면 프론트분들도 처리하기 더더욱 쉽겠죠?ㅎㅎ

public class CustomException extends RuntimeException {

    private final ErrorCode errorCode;

    public CustomException(ErrorCode errorCode) {
       super(errorCode.name());
       this.errorCode = errorCode;
    }

    public ErrorCode getErrorCode() {
       return errorCode;
    }
}

 


그리고, 저 errorcode를 throw할 수 있는 custom exception을 정의해줍니다.

 

여기서 잠깐! RuntimeException을 extends하는 이유!
왜 하필 많은 오류 중 Runtime exception일까! 저는 굉장히 궁금했습니다. 사실 이름만 보면 그냥 Exception이 더 일반적으로 보이는데 쓰면 계속 throw를 하래잖아요! 그래서 패트병 대신 텀블러로 커피를 1회 마시고 GPT선생님께 여쭈어봤죠.

 

역시 우리으 친절하신 즤선생님,,

 

 

RuntimeException과 IllegalArgumentException과 같이 평소에 우리가 사용해도 try-catch 등을 쓰지 않아도 되는 Exception들은 Unchecked Exception이라고 합니다! 그에 반해, 일반 Exception은 Checked Exception이라고 try-catch나 throws를 해야된다고 하네요.

 

그래서 아무튼 Exception이 아닌 것들 중 가장 무난한 RuntimeException을 채택하였습니다.

 

그 후 원래 제가 만들어놨던 GlobalExceptionHandler 안에 제 작고 소중한 CustomException도 추가해줬어요...><

 

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(RuntimeException.class)
	public ResponseEntity<ApiResponseDto> handleRuntimeException(RuntimeException error) {
		ApiResponseDto res = new ApiResponseDto("fail", error.getMessage(), null);
		return new ResponseEntity<>(res, HttpStatus.BAD_REQUEST);
	}
    
    //생략

    @ExceptionHandler(CustomException.class)
    public ResponseEntity<ApiResponseDto<String>> handleCustomException(CustomException error) {
        ApiResponseDto<String> response = new ApiResponseDto<>("fail", error.getErrorCode().getMessage(), null);
        return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
    }
}

 

첫번째 메서드가 제가 직접 사용하던 RuntimeException 이고요, 밑이 제가 이제 사용하는 CustomException입니다!!

사실 기능상의 차이는 없어요~! RuntimeException도 message를 반환하고, 제 custom handler도 완전 같은 리턴타입이기에..

하지만, 귀엽잖아요.

 

다꾸, 폰꾸에 이어 에꾸(에러 꾸미기)까지... 즐겁지않나요?><

 

그리고 이제 Error을 Throw해주는데 사용될 Validator을 만들어줍니다~

@Component
@AllArgsConstructor
public class BattleValidator {

    static Date now = new Date();
    static Calendar calendar = Calendar.getInstance();
    private final BattleRepository battleRepository;

    public void validateOppositeUser(BattleInviteRequest battleInviteRequest, User user) {
       if (battleInviteRequest.getOppositeUserId() == user.getId()) {
          throw new CustomException(ErrorCode.INVALID_USER);
       }
    }

    public void validateTime(int minutes) {
       if (minutes < 10) {
          throw new CustomException(ErrorCode.INVALID_BATTLE_TIME);
       }
    }

 

아까랑 똑같은 코드에 똑같은 결과를 반환하는데, 이렇게 설정하는것이 훨씬 간지작살나지 않나요?

이제 서비스단에 이 Validator 내 메서드를 적용하면...!!

 

@Transactional
@Override
public void registBattle(BattleInviteRequest battleInviteRequest, User user) {

	//생략...

    battleValidator.validateOppositeUser(battleInviteRequest, user);
    battleValidator.validateTime(battleInviteRequest.getTime());
    battleValidator.validateStartTime(battleInviteRequest.getStartDate());
    battleValidator.checkOtherBattles(user, battleInviteRequest.getStartDate(), endDate);
}

 

TADA!!!!!

나만의 귀여운 코꾸(코드꾸미기...), 완성!!!

아까 말도안되는 if문과 비교해보면,, 훨씬 귀엽고 깜찍해졌죠?

 

이렇게 작성한 코드는 후에 다른사람이 보았을 때도 이해하기 쉽고, 유지보수도 용이해집니다~

 

사실 저는 1년전 이맘때쯤, 굉장한 홍머병에 걸렸었는데요,

그때는 플젝때도 남들이 내 코드를 이해하기 어렵고, 건드리지 못하는 것이 뭔가 고수같고 개짱짱맨 코드인 줄 알았답니다...

하하 코드는 무조건 간결!!해야하고 모듈화가 잘 되어있어야합니다~

 

이거는 알림 기능에서도 적용할 수 있는데요!

어떠한 알림을 보낼 지 프론트님님님들과 상의 후

public enum NotificationType {
    BATTLE_REQUEST(0, "%s님이 배틀을 신청했어요! 지금 바로 확인해보세요."),
    LIVE_NOTICE(1, "[%s] 라이브 시작 5분 전입니다!"),
    BATTLE_ACCEPT(2, "[%s] 배틀이 승낙되어 대기중입니다."),
    BATTLE_DECLINE(3, "[%s] 배틀이 거절되었습니다."),
    BATTLE_UNSATISFIED(4, "[%s] 배틀이 인원수를 충족하지 못해 모닥불로 이동하였습니다."),
    BATTLE_SATISFIED(5, "[%s] 배틀이 인원수를 충족하여 불구경 대기중입니다.");
    //todo 인원 수 미달 -> 모닥불 (참여자, 개최자)
    //todo 인원 수 충족 -> 라이브 개최 (참여자, 개최자)

    private final int code;
    private final String messageTemplate;

    NotificationType(int code, String messageTemplate) {
       this.code = code;
       this.messageTemplate = messageTemplate;
    }

    public int getCode() {
       return code;
    }

    public String getMessageTemplate() {
       return messageTemplate;
    }
}

 

이렇게 enum으로 정리해놓으며, 추가/수정하면 편하답니다~

 

그럼 여기서 퀴즈! 알림 메서드들의 문구들을 어떻게하면 더 그럴싸하게 작성할 수 있을까요?(제발도와주세요 저거 넘 구린거같아)

 

그럼 담에 또봐요 안뇽~~~

바바룽

 

문제는 이러하다.

 

 

기존의 알파벳과 다르게 a,b,k,d,e,g,h,i,l,m,n,ng,o,p,r,s,t,u,w,y 순으로만 사용되고,

여기서 눈 여겨 볼 점은

 

1. n과 o 사이의 ng

2. k의 위치

3. ng를 기준으로 앞 c, f, j 뒤 q, v, x, z 부재

 

정도인 것 같다.

풀이의 흐름은 다음과 같다.

 

1. c는 존재하지 않으며 그 위치에 k가 있으므로 입력을 받을 시 c -> k로 치환.

 

2. ng를 처리하기위해 ng 다음 알파벳인 o, p, r, ... , y를 char기준으로 +1씩 늘려줌.

예를 들면 ..., m, n, o(==ng), p(==o), q(==o), s(==r), t(==s), u(==t), ...

 

package baekjoon;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

public class 민식어_1599 {
    static class Word {
        String before, after;
        Word(String before) {
            this.before = before;
        }
    }
    public static void main(String[] args) throws IOException {
        BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
        int N = Integer.parseInt(bf.readLine());
        ArrayList<Word> arr = new ArrayList<>();

        for(int i=0;i<N;i++) {
            String input = bf.readLine();
            Word word = new Word(input);
            word.after = "";

            for(int j=0;j<input.length();j++) {
                if(input.charAt(j)=='g' && j != 0 && input.charAt(j-1)=='n') continue;
                if(input.charAt(j) == 'k') {
                    word.after += "c"; continue;
                } else if(input.charAt(j) >= 'n') {
                    if(input.charAt(j)=='n' && j < input.length()-1 && input.charAt(j+1)=='g') {
                        word.after += "o"; continue;
                    } else if(input.charAt(j)=='n') {
                        word.after += "n"; continue;
                    } else {
                        word.after += Character.toString((char)(input.charAt(j) + 1));
                    }
                } else {
                    word.after += Character.toString(input.charAt(j));
                }
            }
            arr.add(word);
        }

        Collections.sort(arr, new Comparator<Word>() {

            @Override
            public int compare(Word o1, Word o2) {
                return o1.after.compareTo(o2.after);
            }
            
        });
        for(Word w : arr) {
            System.out.println(w.before);
        }
    }
}

 

원래는 그냥 char배열로 받고 출력 전 원래의 단어로 돌릴까 싶었지만, 아무래도 새로운 class로 변경 전 알파벳과 변경 후 알파벳을 각각 저장하는 게 날 것 같았다.

 

아무튼, 성공!

인스타그램이나 유튜브와 같은 매체에서 검색 시,

G O O O O ... L E 와 같이 클릭하여 페이지를 넘기는 형식과 다르게

끝도 없이 스크롤이 생기며 컨텐츠들이 로드되는데요!

 

이런 무한스크롤을 구현하기 위해서 필요한 것이 Pagination 입니다!!

사실 무한스크롤 뿐만 아니라 그냥 값들을 다 받아올 때 Pagination을 쓰는 것을 추천드립니다.

데이터가 몇백,, 몇천을 넘어 몇백만개,, 이거 다 한번에 넘길 거 아니잖아요?

 

모든 코드는 Java, Spring(boot) 개발 환경을 기준으로 작성합니다!!

 

먼저, 프론트로부터 페이지 값을 넘겨받습니다.

가령, 유저가 처음으로 데이터들을 요청을 했을 시 프론트가 백으로 요청해야되는 page 값은 1이되겠죵!(default 값)

무한스크롤로 구현할 시 현재 2페이지 마지막 부분을 유저가 보고있다면, page = 3으로 요청을 해야겠죵

 

여기서 두 가지로 방법이 나뉩니다.

 

1. 처음부터 pageable 개체로 받기

@PostMapping("/get-list-by-pageable")
public ResponseEntity<?> getListByPageable(Pageable pageable, @RequestBody Object otherMethods) {
	Page<?> page = pageServiceImpl.getListByPageable(pageable, otherMethods);
    // 이하생략...
}

 

2. int page, int sort, int size 등 param값으로 넘겨받기

@PostMapping("/get-list-by-params")
public ResponseEntity<?> getListByParams(@RequestParam("page") int page, @RequestParam("size") int size, @RequestParam("sort") int sort, @RequsetBody Object otherMethods) {
	Page<?> page = pageServiceImpl.getListByParams(page, size, sort, otherMethods);
    // 이하 생략...
}

 

사실 뭐가됐듬 상관 없습니다 하지만 전 2번이 더 좋더라고용 처리하기가 쉬워서

 

그 다음, 서비스 단에서 Pageable 객체를 생성합니당

public Page<?> getListByParams(int page, int size, int sort, Object otherMethods) {
	//pageable 객체를 만들어주기~
	Pageable pageable = PageRequestOf(page, size, sort);
    List<?> list = pageMapper.getList(otherMethods);
    
    return new PageImpl<>(list, pageable, list.size());
}

 

만약 본인이 Controller단에서부터 Pageable로 받는다!! 하면 Pageable 객체를 만들어 줄 필요가 없겠죠~~

List를 가져온 다음, 만들거나 받아온 pageable 객체와 함께 Page를 만들어서 return하면 된답니다!

 

Swagger로 돌려보면!

    "pageable": {
        "sort": {
            "empty": true,
            "sorted": false,
            "unsorted": true
        },
        "offset": 0,
        "pageNumber": 0,
        "pageSize": 5,
        "paged": true,
        "unpaged": false
    },
    "last": false,
    "totalElements": 99,
    "totalPages": 20,
    "size": 5,
    "number": 0,
    "sort": {
        "empty": true,
        "sorted": false,
        "unsorted": true
    },
    "first": true,
    "numberOfElements": 99,
    "empty": false

 

이런식으로 list와 함께 현재 페이지 정보가 같이 return되는 것을 알 수 있어요~

참 쉽죠!

 

+ Recent posts