문제
우아한테크코스에서는 교육생(이하 크루) 간 소통 시 닉네임을 사용한다. 간혹 비슷한 닉네임을 정하는 경우가 있는데, 이러할 경우 소통할 때 혼란을 불러일으킬 수 있다.
혼란을 막기 위해 크루들의 닉네임 중 같은 글자가 연속적으로 포함 될 경우 해당 닉네임 사용을 제한하려 한다. 이를 위해 같은 글자가 연속적으로 포함되는 닉네임을 신청한 크루들에게 알려주는 시스템을 만들려고
한다.
신청받은 닉네임 중 같은 글자가 연속적으로 포함 되는 닉네임을 작성한 지원자의 이메일 목록을 return 하도록 solution 메서드를 완성하라.
email.com 도메인으로만 제한한다.| forms | result |
|---|---|
| [ ["jm@email.com", "제이엠"], ["jason@email.com", "제이슨"], ["woniee@email.com", "워니"], ["mj@email.com", "엠제이"], ["nowm@email.com", "이제엠"] ] | ["jason@email.com", "jm@email.com", "mj@email.com"] |
구현 전 고려한 내용
구현 과정 및 피드백 내용
public InputHandler() {
this.br = new BufferedReader(new InputStreamReader(System.in));
}
public String input() {
try {
return br.readLine();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public class OutputHandler {
public void message(String message) {
System.out.print(message);
}
}
❗ Interface 를 사용했다면 추상화와 재사용성이 높은 코드가 될 수 있었을 것이다. BufferedReader 가 아니고 Scanner 를 사용해도 실행될 수 있는 코드로 리팩토링이 필요하다는 지적을 받았다.
public class Crew {
private final String email;
private final String nickname;
public Crew(String email, String nickname) {
this.email = email;
this.nickname = nickname;
}
public String getEmail() {
return email;
}
public String getNickname() {
return nickname;
}
@Override
public String toString() {
return "Crew{" +
"email='" + email + '\'' +
", nickname='" + nickname + '\'' +
'}';
}
}
❗ 필드가 모두 private final 이기 때문에 JAVA 14 부터 지원된 Record 로 만들기도 가능했지만 지식 부족(...)으로 사용하지 못했다. 다음에는 레코드를 꼭 사용해봐야겠다!
public class CrewList {
private List<Crew> crewList;
private List<String> emailList;
private List<String> nicknameList;
private static final CrewList instance = new CrewList();
private CrewList() {
crewList = new LinkedList<>();
this.emailList = getEmailList(crewList);
this.nicknameList = getNicknameList(crewList);
}
public static CrewList getInstance() {
return instance;
}
public List<Crew> getCrewList() {
return crewList;
}
public List<String> getEmailList() {
return emailList;
}
public List<String> getNicknameList() {
return nicknameList;
}
public void setCrewList(Crew crew) {
crewList.add(crew);
}
public List<String> getEmailList(List<Crew> crewList) {
return this.crewList.stream()
.map(Crew::getEmail)
.collect(Collectors.toList());
}
public List<String> getNicknameList(List<Crew> crewList) {
return this.crewList.stream()
.map(Crew::getNickname)
.collect(Collectors.toList());
}
}
❗ getEmailList, getNicknameList 를 통해 여러번 참조하지 않고도 한번에 이메일과 닉네임 리스트에 접근하는 식으로 구현하고 싶었지만 그러지 못했고, 사용하지 않은 메서드가 되었다. 여러번의 참조와 반복문은 덤...💦
❗ 싱글톤 패턴에서 getter 를 사용할 때, 원본을 가져다주지 않도록 해야한다!
public class EmailValidator {
private final String EMAIL_FORMAT = "@email.com";
// 11자 이상, 20자 미만
private final int MIN_LENGTH = 11;
private final int MAX_LENGTH = 20;
public boolean valid(String email) {
return formatValid(email) && sizeValid(email);
}
private boolean formatValid(String email) {
return replace(email).endsWith(EMAIL_FORMAT);
}
private boolean sizeValid(String email) {
return replace(email).length() >= MIN_LENGTH
&& replace(email).length() < MAX_LENGTH;
}
}
NicknameValidator.java 도 같은 방식으로 작성했다.
이번 문제의 가장 핵심인 '닉네임이 2글자 이상 연속으로 겹치는 경우 찾아내기' 로직을 구현한 부분이다.
실행 과정
리스트 길이 검증 ➡ 닉네임과 이메일 형식, 길이 검증 ➡ 중복된 경우를 찾아내서 리스트에 담기 ➡ 컨트롤러에서 리스트를 반환받기 ➡ solution() 메서드에서 size 가 0 이상일 경우 result 를 출력 OR size 가 0 일 경우 '중복없음' 출력
private void isDuplicate(Crew currentCrew, Crew nextCrew) {
String currentNickname = currentCrew.getNickname();
String nextNickname = nextCrew.getNickname();
for (int i = 0; i < currentNickname.length() - SUB_LENGTH; i++) {
String subPrevNickname = currentNickname.substring(i, i + SUB_LENGTH);
if (nextNickname.contains(subPrevNickname)) {
duplicateEmailList.add(currentCrew.getEmail());
duplicateEmailList.add(nextCrew.getEmail());
}
}
}
public List<String> findDuplicateNickname() {
List<Crew> crewList = CrewList.getInstance().getCrewList();
for (int i = 0; i < crewList.size() - 1; i++) {
Crew currentCrew = crewList.get(i);
for (int j = i + 1; j < crewList.size(); j++) {
Crew nextCrew = crewList.get(j);
isDuplicate(currentCrew, nextCrew);
}
}
return duplicateEmailList;
}
❗ 이 메서드는 검증의 역할이기 보다는 로직이기 때문에 Validator.java 클래스가 아니라 Service.java 를 만들어서 구현했어야하지 않냐는 지적을 받았다. 메서드의 큰 역할을 잘 생각해야겠다.
public static String replace(String str) {
return str.replace("\"", "");
}
private String[] parseArray(String inputCrewData) {
String substrCrewData = inputCrewData.substring(2, inputCrewData.length() - 2);
return substrCrewData.split("], ?\\[");
}
private void setCrewList(String inputCrewData) {
for (String crewList : parseArray(inputCrewData)) {
String[] crew = crewList.split(", | ");
validator.crewValid(crew);
CrewList.getInstance().setCrewList(new Crew(crew[0], crew[1])); // [0]="이메일", [1]="닉네임"
}
}
public Controller() {
validator = new Validator();
input = new InputHandler();
output = new OutputHandler();
on();
}
private void on() {
output.message("크루 정보 입력 => ");
String inputCrewData = input.input();
validator.crewListValid(inputCrewData);
setCrewList(inputCrewData);
validator.crewListLengthValid();
solution(); // 여기서 실행!
}
private void solution() {
duplicateList = validator.findDuplicateNickname();
if (duplicateList.size() > 0) {
String result = duplicateList.stream().distinct().sorted().toList().toString();
output.message("result : " + result);
return;
}
output.message("👍 중복없음!");
}
❗ stream() 을 사용하는 경우, 'stream().어쩌구.저쩌구...' 이런식으로 나열하는 게 아니라, 한 메서드마다 엔터를 쳐줘야한다. 이 부분을 고려하지 못했다. 코드의 가독성을 항상 고려해야겠다!
실행결과

회원을 더 추가해도 잘 실행된다😎
알게된 점
// 수정 전
public List<Crew> getCrewList() {
return crewList;
}
// 수정 후
public List<Crew> getCrewList() {
return new LinkedList<>(crewList);
}
이런 유용한 정보를 나눠주셔서 감사합니다.