Day07: 첫 미니 프로젝트 — 인스타 사용자 관리 분석기
안녕하세요! 여러분의 Java 가이드, 홍순구 튜터입니다.
Day 7에 오신 걸 환영합니다!
지난 시간에 우리는 메서드(method) 라는 도구를 손에 넣었어요.
같은 작업에 이름을 붙여두고, 그 이름만 부르면 안에 적힌 절차가 자동으로 실행되는 도구였죠.
덕분에 길고 반복적이던 main이 두세 줄짜리로 깔끔해지는 것도 직접 봤고요.
그런데 지난 시간 마지막에 풀리지 않은 답답함이 두 개 남아 있었던 거, 기억나시나요?
하나는 Day 6 Step 7에서 평행 배열로 추천 사용자를 출력할 때였어요.
"한 사용자 출력에 네다섯 줄이 똑같이 반복되는데, 이걸 어떻게 하나로 묶지?" 하는 답답함이요.
다른 하나는 추천 점수를 계산하던 calculateRecommendScore 메서드였어요.
팔로워, 게시물, 함께 아는 친구, 활동 일수까지 매개변수가 네 개나 줄줄이 늘어서, "이거 자꾸 늘어나는데 괜찮나?" 하는 찜찜함이 있었죠.
오늘은 첫 번째 답답함을 메서드 합성으로 시원하게 풀어드릴 거예요. 두 번째 매개변수 문제도 오늘 다시 만나게 되는데, 진짜 해결책은 다음 시간(Day 8)에 준비해 뒀으니 오늘은 "아, 이게 불편하긴 하구나" 정도만 함께 느껴봅시다.
그리고 오늘은 조금 특별한 날이에요. 새 문법을 외우는 날이 아니라, 지금까지 배운 도구를 한 프로젝트에 전부 꺼내 쓰는 날이거든요. 변수, 배열, 조건문, 반복문, 메서드 — 우리가 Day 1부터 모은 도구가 이미 한 통 가득합니다. 오늘은 새로 외울 게 거의 없어요. 대신 그 도구를 다 꺼내서 작동하는 프로그램 하나를 직접 조립합니다.
지금까지 배운 게 레고 블록 한 통이었다면, 오늘은 그 블록들로 실제 작품 하나를 만드는 날이에요. 블록 하나하나(변수, 배열, 메서드)는 그 자체로는 그냥 부품이지만, 정해진 설계도에 따라 맞춰 끼우면 비로소 "굴러가는 자동차", "서 있는 집" 같은 완성품이 됩니다. 프로그래밍도 똑같아요. 작은 메서드 하나는 부품이고, 그 부품들을 잘 조립하면 하나의 프로그램이 돼요.
오늘의 주제는 "첫 미니 프로젝트 — 인스타 사용자 관리 분석기" 입니다.
🎯 학습 목표
- 평행 배열 여러 개로 사용자 데이터를 한 묶음처럼 관리할 수 있다
- 전체 목록 출력 / 이름 검색 / 팔로워 상위 N명 정렬 메서드를 직접 작성할 수 있다
- 작은 메서드들을 서로 호출(메서드 합성)해서
main을 깔끔하게 유지할 수 있다 - 프로그램 실행 시 외부에서 입력값을 받는
args를 활용할 수 있다 - 추천 점수를 계산하고 점수에 따라 등급을 분류할 수 있다
- IntelliJ 디버거로 한 줄씩 실행하며 코드의 흐름을 눈으로 따라갈 수 있다
- 평행 배열이 여러 개로 늘어날 때의 불편함을 체감하고, 더 나은 방법이 있다는 걸 느낀다
Step 1: "오늘 우리가 만들 작품을 먼저 봅시다" — 인스타 사용자 관리 분석기 소개
자, 본격적으로 코드를 짜기 전에 잠깐 멈춰볼게요. 요리를 배울 때도 완성된 음식 사진을 먼저 보고 "아, 이런 걸 만드는구나" 하고 시작하면 훨씬 따라가기 쉽잖아요. 프로그래밍도 똑같아요. 오늘 우리가 만들 결과물을 먼저 구경하고, 그다음에 한 조각씩 만들어 나갈 거예요.
이번 Step에서는 코드를 거의 쓰지 않아요. 대신 "오늘 뭘 만드는지", "데이터를 어떤 모양으로 준비하는지" 그림을 먼저 머릿속에 그려봅니다.
우리가 만들 프로그램
오늘 만들 건 인스타 사용자 관리 미니 분석기예요. 인스타그램에 있을 법한 사용자 여섯 명의 데이터(팔로워 수, 게시물 수 등)를 프로그램 안에 담아두고, 그 데이터를 가지고 네 가지 일을 할 수 있는 콘솔 프로그램입니다.
- 전체 목록 출력 — 등록된 사용자 전부를 한 줄씩 보여줘요
- 이름으로 검색 — 특정 사용자 한 명의 상세 정보를 찾아줘요
- 팔로워 상위 N명 — 팔로워가 가장 많은 사람들을 순위로 뽑아줘요
- 추천 점수 & 등급 — 각 사용자에게 점수를 매기고 "강력 추천 / 추천 / 보통" 같은 등급을 붙여줘요
말로만 들으면 막연하죠? 실제로 다 만들면 어떤 화면이 나오는지 직접 봅시다.
완성 실행 결과 미리보기
Step 7까지 다 끝내고 프로그램을 실행하면, 콘솔에 이런 결과가 주르륵 나와요.
=== 전체 사용자 목록 ===
@jaehoon_dev 팔로워 1.2K 게시물 42개
@minji_cafe 팔로워 8.5K 게시물 150개
@seungwoo 팔로워 320 게시물 12개
@soyeon_art 팔로워 4.1K 게시물 88개
@wooseok99 팔로워 15.8K 게시물 320개
@hayoung_food 팔로워 2.3K 게시물 67개
=== 팔로워 상위 3명 ===
1위 @wooseok99 (팔로워 15.8K)
2위 @minji_cafe (팔로워 8.5K)
3위 @soyeon_art (팔로워 4.1K)
=== 추천 점수 & 등급 ===
@minji_cafe (팔로워 8.5K) 점수 357점 → 강력 추천
어때요? 그냥 숫자 덩어리가 아니라, 팔로워 1240명을 "1.2K"처럼 사람이 읽기 좋게 줄여주고, 팔로워가 많은 순서대로 1위, 2위, 3위 순위까지 매겨서 보여줍니다. 마지막엔 점수를 계산해서 "이 사용자는 강력 추천이에요" 같은 등급까지 붙여줘요.
이걸 한 방에 다 만드는 게 아니라, 작은 기능 하나씩 차근차근 쌓아 올릴 거예요. 지금 이 화면이 바로 오늘의 목표 지점이라고 생각하시면 됩니다.
데이터는 어떻게 담을까? — 평행 배열 다섯 개
그럼 사용자 여섯 명의 정보를 프로그램 안에 어떻게 담아둘까요?
지난 시간에 배운 평행 배열을 그대로 씁니다.
평행 배열은 "같은 인덱스 i가 한 사람을 가리킨다"는 약속으로 여러 배열을 나란히 두는 방법이었죠.
오늘은 사용자 한 명을 설명하는 정보가 다섯 가지라서, 배열도 다섯 개를 나란히 둡니다.
usernames— 사용자 이름 (예:"jaehoon_dev")followers— 팔로워 수 (예:1240)posts— 게시물 수 (예:42)mutualFriends— 함께 아는 친구 수 (추천 점수 계산용)daysActive— 활동 일수 (추천 점수 계산용)
다섯 개의 배열에서 같은 위치(인덱스 0번)를 꺼내면 한 사람의 정보가 완성돼요.
usernames[0]은 "jaehoon_dev", followers[0]은 1240, posts[0]은 42... 이렇게요.
인덱스 0번을 한 줄로 세로로 읽으면 "jaehoon_dev라는 사람은 팔로워 1240명, 게시물 42개" 가 되는 거죠.
다섯 개의 배열이 같은 인덱스로 한 사람을 가리키는 모습을 그림으로 보면 이래요.
인덱스: 0 1 2 ...
usernames: [ "jaehoon_dev" ][ "minji_cafe" ][ "seungwoo" ]
followers: [ 1240 ][ 8500 ][ 320 ]
posts: [ 42 ][ 150 ][ 12 ]
mutualFriends: [ 8 ][ 23 ][ 2 ]
daysActive: [ 120 ][ 365 ][ 30 ]
└──────┬───────┘
│ 인덱스 0번을 세로로 모으면 → "jaehoon_dev,
│ 팔로워 1240, 게시물 42, 친구 8, 활동 120일"
└─ 한 사람 한 명의 정보가 완성!
"배열이 다섯 개나 되네요?" — 살짝 불편함 예고
여기서 눈치 빠른 분들은 벌써 이런 생각이 드실 거예요. "한 사람을 표현하는데 배열이 다섯 개나 필요하다고요? 좀 번거로운데..."
맞아요. 사실 좀 불편합니다. 사용자 한 명의 정보를 다루려면 이 배열 다섯 개가 항상 같이 붙어 다녀야 하거든요. 메서드에 넘길 때도 다섯 개를 같이 넘겨야 하고, 새 사용자를 추가할 때도 다섯 군데를 똑같이 고쳐야 하고요. 하나라도 빠뜨리면 인덱스가 어긋나서 엉뚱한 정보가 섞여버려요.
🙋 학생 질문 — "튜터님, 이렇게 불편하면 더 좋은 방법은 없나요?"
아주 좋은 질문이에요! 그리고 정확히 맞는 직감입니다.
이렇게 "한 덩어리로 다뤄야 하는 정보"가 여러 개로 흩어져 있을 때, 그걸 하나로 묶어주는 방법이 자바에 따로 있어요. "이름, 팔로워, 게시물, 친구 수, 활동 일수"를 한 상자에 담아서 "사용자 한 명"이라는 통째로 다룰 수 있게 해주는 거죠.
다만 오늘은 그 방법을 아직 배우지 않았으니, 메서드로 정리하는 선에서 최대한 깔끔하게 만들어볼 거예요. 그리고 진짜 해결책은 다음 시간에 만나게 됩니다. 오늘은 "평행 배열로도 할 수는 있지만, 좀 불편하구나" 하는 감각만 챙겨두면 충분해요. 이 불편함을 직접 겪어봐야 다음 시간의 해결책이 얼마나 시원한지 제대로 느낄 수 있거든요.
그래서 오늘은 이 다섯 개의 배열을 메서드로 잘 정리만 하고, 진짜 해결책은 다음 시간으로 미뤄둘게요.
오늘의 작업 로드맵
오늘 하루를 어떻게 쌓아 올릴지 미리 한눈에 보여드릴게요. 한 Step씩 작은 기능을 만들고, 마지막에 전부 합쳐서 위에서 본 완성 화면을 만듭니다.
- Step 2 — 전체 사용자 목록을 출력하는 조회 기능을 만들어요
- Step 3 — 이름으로 검색하고, 팔로워 상위 N명을 뽑는 정렬 기능을 만들어요
- Step 4 — 프로그램 실행 시 외부에서 검색어를 받는
args를 다뤄요 - Step 5 — 추천 점수를 계산하고 등급으로 분류해요
- Step 6~7 — 지금까지 만든 부품을 전부 합쳐 완성하고, 디버거로 실행 흐름을 따라가요
💡 튜터의 결론
오늘은 새 문법을 외우는 날이 아니라, 가진 도구를 전부 꺼내 작동하는 프로그램 하나를 조립하는 날이에요. 작은 메서드들이 모여 하나의 완성품이 되는 과정을 직접 경험하는 게 핵심입니다.
자, 오늘 만들 작품의 모습을 머릿속에 그렸으니, 이제 첫 번째 부품부터 만들러 Step 2로 넘어가 볼까요?
Step 2: "사용자 전부를 한 줄씩 보여줘" — 조회 메서드 만들기
자, 이제 진짜 첫 부품을 깎아볼 차례예요. 오늘 만들 분석기의 가장 기본은 "등록된 사용자 전부를 한 줄씩 화면에 보여주는 것" 이에요. 인스타그램으로 치면 검색창에 아무것도 안 쳤을 때 추천 계정이 쭉 뜨는 그 화면이죠.
지난 시간(Day 6) 마지막에 평행 배열로 사용자를 출력하면서 답답했던 기억, 떠오르시죠?
한 사람을 출력하는 데 비슷한 줄이 네다섯 개씩 반복돼서 main이 금세 지저분해졌어요.
오늘은 그 출력 로직을 메서드 하나로 쏙 빼내서 main을 깔끔하게 만들어볼 거예요.
먼저 팔로워 숫자를 예쁘게 — formatFollowers
본격적인 목록 출력에 들어가기 전에, 작은 도우미 메서드 하나를 먼저 만들게요.
팔로워가 1240명이면 화면에 그냥 1240이라고 띄워도 되지만,
인스타그램처럼 1.2K라고 줄여서 보여주면 훨씬 보기 좋잖아요.
이거, 사실 지난 시간에 비슷한 걸 해봤어요.
Day 6에서 숫자를 보기 좋게 줄여주던 formatCount 패턴, 기억나시죠?
그 패턴을 사용자 관리 분석기에 맞게 그대로 가져와서 이름만 formatFollowers로 바꾼 거예요.
// day07/InstagramUserManager.java
// 1000 이상이면 "1.2K" 처럼 줄여서 보여줘요 — 지난 시간 formatCount 패턴을 그대로 활용
static String formatFollowers(int count) {
if (count >= 1_000) {
return (count / 1_000) + "." + ((count / 100) % 10) + "K";
}
return String.valueOf(count);
}
이 메서드가 하는 일을 한 줄씩 따라가 볼게요.
count가 1_000보다 작으면(예: 320) 그냥 숫자를 글자로 바꿔서(String.valueOf) 돌려줘요.
1_000 이상이면(예: 1240) 1240 / 1000으로 정수 나눗셈을 해서 앞자리 1을 뽑고,
(1240 / 100) % 10으로 소수점 아랫자리 2를 뽑아서 "1.2K"를 만들어요.
여기서 숫자 사이의 밑줄(1_000)이 낯설 수 있는데, 이건 자바가 큰 숫자를 읽기 좋게 해주는 장치예요.
우리가 큰돈을 적을 때 1,000처럼 쉼표를 찍듯이, 자바에선 밑줄을 쓸 수 있어요.
1_000이나 1000이나 컴퓨터에겐 완전히 같은 값이고, 그저 사람 눈에 잘 읽히라고 넣는 거예요.
전체 목록을 출력하는 printAllUsers
도우미가 준비됐으니, 이제 진짜 목록 출력 메서드를 만들어요. 사용자 배열을 처음부터 끝까지 한 명씩 돌면서, 한 줄에 이름·팔로워·게시물을 찍어줍니다.
// 전체 사용자를 한 명씩 순회하며 출력 — 안에서 formatFollowers 를 불러요 (메서드가 메서드를 호출)
static void printAllUsers(String[] usernames, int[] followers, int[] posts) {
for (int i = 0; i < usernames.length; i++) {
System.out.println("@" + usernames[i]
+ " 팔로워 " + formatFollowers(followers[i])
+ " 게시물 " + posts[i] + "개");
}
}
자, 여기서 오늘의 핵심 개념이 처음 등장해요.
잘 보면 printAllUsers 안에서 방금 만든 formatFollowers(followers[i])를 부르고 있어요.
큰 일을 하는 메서드(전체 목록 출력)가, 작은 일을 하는 메서드(팔로워 숫자 다듬기)를 불러서 쓰는 거죠.
이렇게 메서드가 다른 메서드를 호출하면서 기능을 합치는 걸 메서드 합성이라고 불러요.
지난 시간에 "큰 일을 하는 메서드가 작은 일을 하는 메서드를 부른다"는 합성 패턴을 배웠는데,
오늘은 그걸 사용자 관리라는 실제 도메인에 적용한 거예요.
printAllUsers는 "어떻게 팔로워 숫자를 1.2K로 줄이지?"를 전혀 고민하지 않아요.
그건 formatFollowers가 알아서 할 일이라고 믿고, 그냥 이름만 부르면 되니까요.
main은 어떻게 깔끔해질까
이제 main에서 이 메서드를 부르는 모습을 볼게요.
Day 5에서는 사용자 목록을 출력하려면 for문과 출력 코드를 main 안에 직접 다 적어야 했어요.
그런데 출력 로직을 printAllUsers로 빼냈더니, main에선 딱 두 줄이면 끝나요.
System.out.println("=== 전체 사용자 목록 ===");
printAllUsers(usernames, followers, posts);
for문도, 복잡한 문자열 조합도 전부 printAllUsers 안으로 들어갔어요.
main은 그냥 "제목 한 줄 찍고, 전체 목록 출력해줘" 하고 지시만 하면 됩니다.
나중에 main을 다시 읽는 사람도 "아, 여기서 전체 목록을 보여주는구나" 하고 한눈에 이해할 수 있죠.
이게 바로 메서드로 추출했을 때의 가장 큰 이득이에요. 읽기 쉬워지는 것.
호출 흐름을 그림으로 보면 이렇게 생겼어요.
main
│
│ printAllUsers(usernames, followers, posts) 호출
▼
printAllUsers ── for문으로 사용자 6명을 한 명씩 순회
│
│ 한 명마다 formatFollowers(팔로워수) 호출
▼
formatFollowers ── 1240 → "1.2K" 로 변환해서 돌려줌
main은 printAllUsers 한 명만 부르고, printAllUsers는 사람 수만큼 formatFollowers를 부르고요.
큰 부품이 작은 부품을 부르고, 작은 부품이 더 작은 일을 처리하는 구조죠.
실행하면 이렇게 나와요
이 두 메서드까지만 만들고 실행하면, 화면 윗부분에 전체 목록이 이렇게 찍혀요.
=== 전체 사용자 목록 ===
@jaehoon_dev 팔로워 1.2K 게시물 42개
@minji_cafe 팔로워 8.5K 게시물 150개
@seungwoo 팔로워 320 게시물 12개
@soyeon_art 팔로워 4.1K 게시물 88개
@wooseok99 팔로워 15.8K 게시물 320개
@hayoung_food 팔로워 2.3K 게시물 67개
팔로워가 320인 seungwoo는 천 명이 안 되니까 그대로 320으로,
나머지는 1.2K, 8.5K처럼 줄여서 나오는 게 보이시죠?
formatFollowers가 사람마다 알아서 판단해서 다르게 보여준 결과예요.
💡 튜터의 결론
메서드 합성이란 "큰 일을 하는 메서드가 작은 일을 하는 메서드를 부르는 것"이에요.
printAllUsers가formatFollowers를 부르듯, 각자 자기 할 일만 책임지게 나누면main은 지시만 내리는 깔끔한 코드가 됩니다.
첫 부품이 완성됐어요! 이제 목록을 보여주는 걸 넘어서, "한 명을 콕 집어 찾고, 인기 순위까지 매기는" 더 똑똑한 기능을 만들러 Step 3로 가봅시다.
Step 3: "한 명을 콕 찾고, 인기 순위를 매겨줘" — 검색과 정렬
전체 목록을 보여주는 것까지 했으니, 이제 한 단계 더 똑똑한 기능을 만들어요. 인스타그램에서 우리가 자주 하는 두 가지 행동이 있죠. 하나는 검색창에 이름을 쳐서 특정 사용자 한 명을 찾는 것, 다른 하나는 팔로워가 많은 인기 계정 순위를 보는 것이에요.
이번 Step에서 이 두 기능을 메서드로 만들어봅니다.
하나는 "이름으로 찾기"(searchUserByName), 다른 하나는 "팔로워 상위 N명 뽑기"(findTopFollowers)예요.
이름으로 찾기 — 그리고 "못 찾으면 -1"
먼저 검색 메서드예요. 사용자 이름을 하나 받아서, 그 이름이 배열의 몇 번째에 있는지(인덱스)를 돌려줍니다.
// day07/InstagramUserManager.java
// 이름으로 사용자를 찾아 인덱스를 돌려줘요. 없으면 -1.
// (배열에서 "못 찾았다" 를 표현할 때 -1 인덱스를 쓰는 건 아주 흔한 약속이에요)
static int searchUserByName(String[] usernames, String target) {
for (int i = 0; i < usernames.length; i++) {
if (usernames[i].equals(target)) {
return i;
}
}
return -1;
}
지난 시간에 배열을 메서드에 넘기면 복사가 아니라 같은 배열을 본다고 그림으로 배웠죠?
여기서도 usernames 배열을 그대로 넘겨서, 그 안을 처음부터 훑으며 찾는 이름과 같은지 비교해요.
이 메서드에서 꼭 짚고 갈 게 하나 있어요. 바로 return -1 이에요.
배열을 끝까지 다 뒤졌는데도 찾는 이름이 없으면, 마지막에 -1을 돌려줍니다.
"왜 하필 -1이냐"고요? 생각해보면 배열의 인덱스는 항상 0, 1, 2, ...처럼 0 이상이잖아요.
그러니까 절대 나올 수 없는 음수 -1을 "못 찾았다"는 신호로 쓰는 거예요.
이건 자바 문법이 강제하는 규칙이 아니라, 개발자들 사이의 흔한 약속이에요.
배열에서 뭔가를 찾았는데 없을 때 -1을 돌려주는 건 워낙 널리 쓰여서,
다른 개발자가 코드를 봐도 "아, -1이면 못 찾은 거구나" 하고 바로 알아챈답니다.
그래서 이 메서드를 받아 쓰는 쪽에서는 if (idx == -1)로 "못 찾은 경우"를 따로 처리해주면 돼요.
팔로워 상위 N명 뽑기 — Arrays.sort를 못 쓰는 이유
다음은 인기 순위예요. 팔로워가 가장 많은 사람 3명을 순서대로 뽑아내야 해요.
"어? 정렬이면 자바에 Arrays.sort라는 게 있다던데, 그거 쓰면 되지 않나요?"
좋은 생각이에요. 그런데 우리 상황엔 안타깝게도 그대로 쓸 수가 없어요.
Arrays.sort(followers)를 하면 팔로워 숫자 배열은 깔끔하게 정렬되겠죠.
하지만 우리는 평행 배열을 쓰고 있잖아요.
followers만 정렬해서 순서가 바뀌면, usernames나 posts는 그대로 있으니까
인덱스가 어긋나서 "8500 팔로워를 가진 사람이 누구지?"를 알 수 없게 돼요.
이름과 팔로워 숫자가 따로 놀게 되는 거죠. 이게 평행 배열의 대표적인 불편함이에요.
그래서 우리는 숫자 자체를 정렬하는 대신, "가장 큰 값이 있는 위치(인덱스)를 N번 골라내는" 방식을 직접 만들 거예요.
// 팔로워가 가장 많은 상위 n명의 "인덱스" 를 배열로 돌려줘요.
// 평행 배열이라 Arrays.sort 로 바로 정렬할 수 없으니,
// "가장 큰 값의 위치를 n번 골라내는" 방식으로 직접 구현해요.
static int[] findTopFollowers(String[] usernames, int[] followers, int n) {
int[] result = new int[n];
boolean[] used = new boolean[followers.length]; // 이미 뽑은 위치는 다시 안 고르도록 표시
for (int rank = 0; rank < n; rank++) {
int maxIndex = -1;
for (int i = 0; i < followers.length; i++) {
if (used[i]) {
continue; // 이미 뽑힌 사람은 건너뛰어요
}
if (maxIndex == -1 || followers[i] > followers[maxIndex]) {
maxIndex = i;
}
}
result[rank] = maxIndex;
used[maxIndex] = true; // 방금 1등으로 뽑았으니 다음 번엔 제외
}
return result;
}
코드가 좀 길어 보이지만, 아이디어는 단순해요. 한 줄씩 따라가 볼게요.
먼저 used라는 boolean 배열을 하나 준비해요.
이건 "이 사람은 이미 순위에 뽑혔어"를 표시해두는 체크리스트예요.
사람 수만큼 false로 시작했다가, 누군가를 뽑을 때마다 그 사람의 칸을 true로 바꿔요.
그다음 바깥쪽 for문이 순위 개수(n번)만큼 돌아요. 1위 뽑고, 2위 뽑고, 3위 뽑고요.
안쪽 for문은 "아직 안 뽑힌 사람 중에서 팔로워가 가장 많은 사람"을 찾아요.
이미 used[i]가 true인 사람은 continue로 건너뛰어요. 이미 뽑혔으니까요.
한 바퀴 다 돌면 maxIndex에 그 회전의 1등 위치가 담기고, 그 자리를 used에 true로 표시해요.
한 명씩 골라내는 과정을 눈으로 보기
말로만 들으면 헷갈리니까, 우리 데이터로 직접 따라가 봅시다.
팔로워 숫자가 wooseok99(15800), minji_cafe(8500), soyeon_art(4100) 순으로 많다고 할 때,
상위 3명을 뽑는 과정은 이렇게 흘러가요.
[1회전] 안 뽑힌 사람 전부 비교 → 가장 큰 15800 (wooseok99) 선택
used 표시: wooseok99 ✓
순위 결과: 1위 → wooseok99
[2회전] wooseok99 는 건너뛰고 나머지 비교 → 가장 큰 8500 (minji_cafe) 선택
used 표시: wooseok99 ✓ minji_cafe ✓
순위 결과: 2위 → minji_cafe
[3회전] 위 둘은 건너뛰고 나머지 비교 → 가장 큰 4100 (soyeon_art) 선택
used 표시: wooseok99 ✓ minji_cafe ✓ soyeon_art ✓
순위 결과: 3위 → soyeon_art
매 회전마다 "아직 안 뽑힌 사람 중 최댓값"을 골라서 순위에 추가하고,
그 사람에게 used 도장을 찍어 다음 회전에선 빠지게 하는 거예요.
이렇게 하면 숫자 배열을 뒤섞지 않고도 순위를 뽑을 수 있어서, 평행 배열의 인덱스가 어긋날 걱정이 없어요.
여기서도 maxIndex를 처음에 -1로 시작했죠?
검색 메서드에서 본 것과 같은 약속이에요. "아직 아무도 못 골랐다"는 신호로 -1을 쓴 거고,
첫 비교에서 maxIndex == -1이면 무조건 그 사람을 후보로 잡는 거예요.
🙋 학생 질문 — "튜터님, 왜 사람 이름이 아니라 인덱스를 돌려주나요?"
좋은 질문이에요! findTopFollowers는 사람 이름("wooseok99")이 아니라 인덱스(4)를 돌려줘요.
왜 그럴까요?
만약 이름만 돌려주면, 그 사람의 게시물 수나 활동 일수는 다시 찾아야 해요.
하지만 인덱스를 돌려주면 평행 배열의 강력함이 살아나요.
인덱스 4 하나만 있으면 usernames[4], followers[4], posts[4]...
다섯 배열 어디서든 그 사람의 모든 정보를 바로 꺼낼 수 있거든요.
그래서 순위를 뽑을 때 "이름"이 아니라 "위치(인덱스)"를 돌려주면, 나중에 그 사람에 대해 무슨 정보가 더 필요하든 한 번에 꺼낼 수 있어서 훨씬 유연해요.
실행하면 이렇게 나와요
이 두 메서드까지 만들고 상위 3명을 출력하면, 화면에 이렇게 순위가 찍혀요.
=== 팔로워 상위 3명 ===
1위 @wooseok99 (팔로워 15.8K)
2위 @minji_cafe (팔로워 8.5K)
3위 @soyeon_art (팔로워 4.1K)
findTopFollowers가 돌려준 인덱스 배열로 usernames와 followers에서 정보를 꺼내고,
팔로워 숫자는 Step 2에서 만든 formatFollowers로 다시 15.8K처럼 다듬어서 보여줬어요.
앞에서 만든 부품을 여기서 또 가져다 쓰는 거죠. 메서드 합성의 힘이 또 한 번 발휘됐네요.
💡 튜터의 결론
배열에서 "못 찾았다"는 신호로는
-1을 쓰는 게 흔한 약속이에요. 그리고 평행 배열은 숫자만 따로 정렬하면 인덱스가 어긋나니까, 값을 뒤섞는 대신 "최댓값의 위치를 N번 골라내는" 방식으로 순위를 뽑습니다.
검색과 순위 기능까지 완성됐어요! 이제 프로그램을 실행할 때 외부에서 검색어를 받아오는 방법을 다음 Step에서 다뤄볼게요.
Step 4: "실행할 때 검색어를 같이 건네줘" — main의 args 활용
자, 지금까지 만든 검색 메서드(searchUserByName)는 잘 동작하긴 하는데, 한 가지 답답한 점이 있어요.
"누구를 찾을지"가 코드 안에 적혀 있다는 거예요.
다른 사람을 찾고 싶을 때마다 코드를 고쳐서 다시 실행해야 한다면 영 불편하잖아요.
그래서 이번엔 프로그램을 실행할 때, 찾을 이름을 바깥에서 같이 건네주는 방법을 배워볼 거예요. 코드는 그대로 두고, 실행할 때마다 검색어만 바꿔서 다른 사용자를 찾을 수 있게요.
드디어 그 args를 쓸 차례
지난 시간(Day 6)에 main의 정식 형태를 배우면서 이런 모습을 봤죠.
public static void main(String[] args) {
그때 제가 "이 괄호 안의 args는 지금은 그냥 따라 적어두고, 나중에 써먹는 날이 온다"고 했던 거 기억나시나요?
바로 오늘이 그날이에요.
이 args가 사실은 프로그램을 실행할 때 외부에서 건네주는 값들을 담는 상자였어요.
누군가 프로그램을 실행하면서 "이 값도 같이 받아!" 하고 건네준 문자열들이 이 args 안에 차곡차곡 담깁니다.
args는 새 문법이 아니라 그냥 배열이에요
여기서 가장 마음 편한 사실 하나.
args는 새로 외울 문법이 전혀 아니에요.
타입을 다시 보세요. String[] args — 네, 우리가 며칠째 다룬 그 문자열 배열이에요.
그러니까 배열에 쓰던 도구가 args에도 그대로 통합니다.
args.length— 건네받은 값이 몇 개인지 (배열 길이 그대로)args[0]— 첫 번째로 건네받은 값 (배열의 0번 칸 그대로)args[1]— 두 번째로 건네받은 값
지난 시간까지 배운 배열 지식이 그대로 적용되니까, "아 이거 그냥 배열이구나" 하고 마음 놓으셔도 돼요.
실행 방법에 따라 args가 어떻게 채워질까
args에 값이 들어오는 모습을 그림으로 보면 이해가 빨라요.
검색어 없이 그냥 실행하면 빈 배열이 들어오고, 검색어를 같이 건네주면 그 값이 0번 칸에 담겨요.
[검색어 없이 실행]
건네준 값: (없음)
args → [ ] 길이 0 (args.length == 0)
[검색어 "minji_cafe" 와 함께 실행]
건네준 값: minji_cafe
args → [ "minji_cafe" ] 길이 1 (args[0] == "minji_cafe")
그럼 이 값을 실제로 어디에 적느냐면, IntelliJ에서는 실행 설정의 "Program arguments" 칸에 적어주면 돼요.
이 칸을 비워두고 실행하면 위 그림의 첫 번째 경우(빈 배열),
이 칸에 minji_cafe라고 적고 실행하면 두 번째 경우(args[0]에 담김)가 되는 거죠.
코드는 한 글자도 안 고치고, 이 칸의 내용만 바꿔서 다른 사용자를 찾을 수 있어요.
args가 있느냐 없느냐로 갈림길을 만든다
이제 main 안에서 args를 어떻게 활용하는지 봅시다.
핵심 아이디어는 간단해요. 검색어를 건네받았는지 아닌지에 따라 다르게 행동하는 것이에요.
// day07/InstagramUserManager.java
if (args.length == 0) {
// 검색어가 없으면 — 전체 사용자 목록 + 추천 목록을 보여줘요 (기본 동작)
System.out.println("=== 전체 사용자 목록 ===");
printAllUsers(usernames, followers, posts);
// ... (상위 3명, 추천 점수 출력) ...
} else {
// 검색어가 있으면 — args[0] 을 이름으로 찾아 상세 정보를 보여줘요
String target = args[0];
int idx = searchUserByName(usernames, target);
if (idx == -1) {
System.out.println("'" + target + "' 사용자를 찾을 수 없어요.");
// ... (등록된 사용자 목록 안내) ...
} else {
// ... 찾았으면 상세 정보 출력 ...
System.out.println("=== @" + usernames[idx] + " 상세 정보 ===");
System.out.println("팔로워 : " + formatFollowers(followers[idx]));
// ... (게시물 / 친구 / 활동 / 추천 점수) ...
}
}
코드의 큰 줄기를 따라가 볼게요.
맨 먼저 args.length == 0인지 확인해요. "건네받은 검색어가 하나도 없나?"를 묻는 거죠.
비어 있으면(length == 0) 검색어가 없다는 뜻이니까, Step 2·3에서 만든 전체 목록과 추천 목록을 쭉 보여주는 기본 동작으로 갑니다.
반대로 검색어가 있으면(else), args[0]으로 그 값을 꺼내서 target에 담고,
Step 3에서 만든 searchUserByName을 그대로 불러서 그 사용자가 몇 번째에 있는지 찾아요.
여기서 앞 Step에서 만든 부품이 또 등장하죠?
검색 결과가 -1이면(못 찾음) 친절한 안내 메시지를 띄우고,
-1이 아니면(찾음) 그 인덱스로 다섯 배열에서 정보를 싹 꺼내 상세 화면을 보여줘요.
Step 3에서 "-1은 못 찾았다는 약속"이라고 했던 게 여기서 그대로 쓰이는 거예요.
실행 결과 두 가지
같은 프로그램인데, 검색어를 건네주느냐 마느냐에 따라 화면이 완전히 달라져요.
먼저 "Program arguments" 칸을 비우고 실행하면(검색어 없음), 기본 동작인 전체 목록이 나와요.
=== 전체 사용자 목록 ===
@jaehoon_dev 팔로워 1.2K 게시물 42개
@minji_cafe 팔로워 8.5K 게시물 150개
@seungwoo 팔로워 320 게시물 12개
@soyeon_art 팔로워 4.1K 게시물 88개
@wooseok99 팔로워 15.8K 게시물 320개
@hayoung_food 팔로워 2.3K 게시물 67개
... (상위 3명, 추천 점수도 이어서 출력)
이번엔 같은 칸에 minji_cafe라고 적고 실행하면, 그 한 명의 상세 정보만 나와요.
=== @minji_cafe 상세 정보 ===
팔로워 : 8.5K
게시물 : 150개
함께 아는 친구: 23명
활동 일수 : 365일
추천 점수 : 357점 → 강력 추천
코드는 똑같은데, 건네준 검색어 하나로 프로그램이 전혀 다른 일을 해냈어요.
이게 args의 힘이에요. 코드를 고치지 않고도 실행할 때마다 다른 입력으로 다른 결과를 만들 수 있는 거죠.
🙋 학생 질문 — "튜터님, 프로그램이 실행되는 도중에 사용자한테 직접 물어볼 수는 없나요?"
아주 좋은 질문이에요! 지금 우리가 쓴 args는 실행을 시작할 때 미리 건네주는 방식이에요.
그런데 프로그램이 돌아가는 도중에 "이름을 입력하세요" 하고 멈춰서, 사용자가 직접 타이핑한 걸 받아오는 방법도 따로 있어요.
콘솔 화면에서 사용자와 주고받는 입력을 본격적으로 다루는 건 나중에 배울 거예요.
오늘은 실행할 때 미리 건네주는 args만으로도 충분해요.
"코드를 안 고치고도 외부에서 값을 받아 동작을 바꾼다"는 감각을 먼저 챙기는 게 이번 Step의 목표거든요.
💡 튜터의 결론
args는 새 문법이 아니라 우리가 계속 다뤄온String[]배열 그대로예요. 실행할 때 외부에서 값을 건네받아args[0]으로 꺼내 쓰면, 코드를 고치지 않고도 실행할 때마다 다른 입력으로 프로그램의 동작을 바꿀 수 있습니다.
검색어를 외부에서 받는 것까지 됐으니, 이제 상세 화면에 등장했던 "추천 점수 357점"이 대체 어떻게 계산된 건지 다음 Step에서 직접 만들어볼게요.
Step 5: "이 사람, 얼마나 추천할 만해?" — 점수 계산과 등급 분류
방금 Step 4의 상세 화면에서 minji_cafe의 "추천 점수 357점 → 강력 추천"이라는 줄을 봤죠?
이번 Step에서는 바로 그 점수가 어떻게 나오는지, 그리고 그 점수를 어떻게 "강력 추천" 같은 등급으로 바꾸는지 만들어봅니다.
점수를 매기는 일을 두 부품으로 나눌 거예요.
하나는 숫자 정보들을 섞어서 점수를 계산하는 메서드(calculateRecommendScore),
다른 하나는 그 점수를 받아서 등급 글자로 바꿔주는 메서드(classifyScore)예요.
네 가지 정보를 섞어 점수를 만든다
먼저 점수 계산 메서드부터 봅시다. 한 사람을 추천할 만한지 판단하려면 정보 하나로는 부족해요. 팔로워도 많이 봐야 하고, 게시물도 꾸준히 올리는지, 나랑 친구가 겹치는지, 얼마나 오래 활동했는지를 두루 봐야죠. 그래서 이 네 가지를 한꺼번에 받아서 점수로 버무립니다.
// day07/InstagramUserManager.java
// 추천 점수 계산 — 네 가지 정보를 가중합으로 섞어요 (숫자가 클수록 더 추천)
static int calculateRecommendScore(int followers, int posts, int mutualFriends, int daysActive) {
int score = 0;
score = score + followers / 100; // 팔로워 100명당 1점
score = score + posts / 5; // 게시물 5개당 1점
score = score + mutualFriends * 10; // 함께 아는 친구는 가중치 큼 (1명당 10점)
score = score + daysActive / 30; // 활동 30일당 1점
return score;
}
각 줄이 무슨 일을 하는지 하나씩 읽어볼게요.
followers / 100— 팔로워 100명마다 1점. 팔로워가 8500명이면 85점이에요.posts / 5— 게시물 5개마다 1점. 꾸준히 올리는 사람일수록 점수가 쌓여요.mutualFriends * 10— 함께 아는 친구는 1명당 10점. 곱하기를 쓴 거 보이시죠? 그만큼 가중치를 크게 줬어요. 나랑 친구가 겹치는 사람일수록 추천할 가치가 크다고 본 거예요.daysActive / 30— 활동 30일(한 달)마다 1점. 오래 활동한 계정일수록 신뢰가 쌓이니까요.
이 네 점수를 다 더한 게 최종 추천 점수예요. 이렇게 여러 정보에 각각 다른 비중을 두고 합치는 걸 가중합이라고 불러요. "어떤 정보는 더 중요하게, 어떤 정보는 덜 중요하게" 무게를 다르게 줘서 더하는 거죠.
여기서 Day 2·3에서 배운 정수 나눗셈을 다시 만나요.
8500 / 100은 85로 깔끔하게 떨어지지만, 만약 8550 / 100이었다면 어땠을까요?
정수 나눗셈이라 소수점 아래(0.5)는 그냥 버려지고 85가 돼요.
점수 계산에서는 이 "소수점 버림"이 오히려 편해요. 점수는 어차피 깔끔한 정수로 다루는 게 보기 좋으니까요.
점수로 직접 계산해보기 — minji_cafe의 357점
말로만 보면 와닿지 않으니, Step 4 상세 화면에 나왔던 minji_cafe로 직접 계산해봅시다.
minji_cafe의 정보는 팔로워 8500명, 게시물 150개, 함께 아는 친구 23명, 활동 365일이었어요.
팔로워 : 8500 / 100 = 85점
게시물 : 150 / 5 = 30점
친구 : 23 * 10 = 230점 ← 가중치가 커서 점수 비중도 큼
활동 : 365 / 30 = 12점 (정수 나눗셈이라 12.16... 의 소수점은 버려짐)
─────────────────────────────
합계 = 85 + 30 + 230 + 12 = 357점
네 점수를 다 더하니 정확히 357점이 나왔어요. Step 4 화면에서 봤던 그 숫자가 바로 이렇게 계산된 거예요. 특히 친구 점수 230점이 전체의 절반 넘게 차지하는 게 보이시죠? 가중치를 크게 준 효과예요.
점수를 등급 글자로 바꾸기 — classifyScore
이제 357이라는 숫자만 덩그러니 있으면 사람이 한눈에 알아보기 어려워요. "357점이면 좋은 거야 나쁜 거야?" 싶잖아요. 그래서 점수를 받아서 "강력 추천" 같은 사람이 읽기 좋은 등급 글자로 바꿔주는 메서드를 만들어요.
// 점수를 사람이 읽기 좋은 등급 문자열로 바꿔줘요
static String classifyScore(int score) {
if (score >= 300) {
return "강력 추천";
} else if (score >= 150) {
return "추천";
} else if (score >= 70) {
return "보통";
} else {
return "관심 낮음";
}
}
이건 Day 3에서 배운 if / else if 사다리예요.
점수가 높은 구간부터 차례로 검사하는 게 핵심이에요.
300점 이상이면 → "강력 추천"- 거기 안 걸리고
150점 이상이면 → "추천" - 거기도 안 걸리고
70점 이상이면 → "보통" - 어디에도 안 걸리면 → "관심 낮음"
점수 구간을 막대로 그려보면 이런 모습이에요.
점수: 0 ─────── 70 ──────── 150 ──────── 300 ───────→
│ 관심 낮음 │ 보통 │ 추천 │ 강력 추천
▲
minji_cafe 357점 → 강력 추천
minji_cafe의 357점을 이 메서드에 넣으면, 맨 위 score >= 300에 딱 걸려서 "강력 추천"을 돌려줘요.
Step 4 상세 화면의 "357점 → 강력 추천"이 이렇게 두 부품(calculateRecommendScore → classifyScore)을 거쳐 완성된 거예요.
다시 만난 매개변수 네 개의 답답함
자, 여기서 오프닝에서 예고했던 그 찜찜함을 직접 마주할 차례예요.
calculateRecommendScore의 첫 줄을 다시 한번 볼까요?
static int calculateRecommendScore(int followers, int posts, int mutualFriends, int daysActive) {
매개변수가 네 개나 줄줄이 늘어서 있죠. 지금이야 네 개라 그럭저럭 버틸 만한데, 점수에 반영할 정보가 더 생기면 어떻게 될까요? "최근 게시물 좋아요 수도 넣자", "스토리 활동도 보자" 하다 보면 매개변수가 다섯 개, 여섯 개로 계속 늘어요.
그러면 이 메서드를 부르는 쪽에서 진짜 헷갈리기 시작해요.
calculateRecommendScore(8500, 150, 23, 365) 이렇게 숫자만 줄줄이 적다 보면,
"어? 두 번째가 게시물이었나 친구였나?" 하고 순서가 헷갈려서 엉뚱한 값을 넣기 쉬워요.
이게 바로 평행 배열의 불편함(흩어진 정보)과 똑같은 뿌리에서 나온 답답함이에요. "한 사람에 속한 정보들"이 하나로 묶여 있지 않고 따로따로 넘겨지니까 자꾸 어긋날 위험이 생기는 거죠.
이 답답함의 진짜 해결책은 다음 시간(Day 8)에 준비해 뒀어요. "이름, 팔로워, 게시물, 친구, 활동 일수"를 한 덩어리로 묶어서 통째로 다루는 방법을요. 오늘은 "아, 매개변수가 자꾸 늘어나는 게 확실히 불편하구나" 하는 감각만 챙겨두면 충분해요. 이 불편함을 직접 겪어봐야 다음 시간의 해결책이 얼마나 시원한지 제대로 느낄 수 있거든요.
💡 튜터의 결론
추천 점수는 여러 정보에 서로 다른 비중을 두고 더하는 가중합으로 계산하고(
calculateRecommendScore), 그 점수를if / else if사다리로 등급 글자로 바꿔요(classifyScore). 다만 매개변수가 네 개나 늘어선 모습에서 "정보를 하나로 묶고 싶다"는 답답함이 또 고개를 드는데, 그 해결책은 다음 시간에 만나게 됩니다.
점수와 등급까지 만들었으니, 이제 오늘 만든 부품을 전부 모아 완성하고 실행 흐름을 따라가는 일만 남았어요. 다음 Step으로 가봅시다.
Step 6: "지금까지 만든 걸 한 줄에 모아줘" — 추천 목록 완성
드디어 오늘의 클라이맥스예요.
Step 2부터 5까지 우리는 작은 부품을 하나씩 깎아 만들었어요.
팔로워 숫자를 다듬는 formatFollowers, 점수를 계산하는 calculateRecommendScore, 점수를 등급으로 바꾸는 classifyScore까지요.
이번 Step에서는 이 부품들을 한 메서드 안에서 전부 모아 추천 목록 한 표를 완성합니다.
말하자면 지금까지는 자동차 부품(바퀴, 엔진, 핸들)을 따로따로 만든 거고, 이번 Step에서는 그 부품들을 한곳에 모아 조립 라인에 올리는 셈이에요.
부품을 모으는 종합 메서드 — printRecommendations
사용자 여섯 명 각각에 대해 점수를 계산하고, 등급을 붙이고, 팔로워 숫자를 다듬어서 한 줄로 출력해야 해요.
이 일을 하나로 묶은 게 printRecommendations 메서드예요.
// day07/InstagramUserManager.java
// 종합 메서드 — 한 줄 안에서 calculateRecommendScore → classifyScore → formatFollowers 를
// 줄줄이 불러요. 작은 부품들이 합쳐져 "추천 목록 한 표" 가 완성되는 클라이맥스예요.
static void printRecommendations(String[] usernames, int[] followers, int[] posts,
int[] mutualFriends, int[] daysActive) {
for (int i = 0; i < usernames.length; i++) {
int score = calculateRecommendScore(followers[i], posts[i],
mutualFriends[i], daysActive[i]);
String grade = classifyScore(score);
System.out.println("@" + usernames[i]
+ " (팔로워 " + formatFollowers(followers[i]) + ")"
+ " 점수 " + score + "점"
+ " → " + grade);
}
}
이 메서드의 for문 안을 자세히 보면, 한 사람을 처리하는 데 우리가 만든 부품 세 개가 줄줄이 등장해요.
먼저 calculateRecommendScore(...)로 그 사람의 점수를 계산해서 score에 담아요.
그다음 그 score를 classifyScore(score)에 넘겨서 "강력 추천" 같은 등급 글자를 grade에 담고요.
마지막으로 출력할 때 formatFollowers(followers[i])로 팔로워 숫자를 8.5K처럼 다듬어 끼워 넣어요.
한 사람당 부품 세 개가 차례로 일을 하고, 그 결과가 한 줄로 합쳐지는 거예요.
한 사람을 처리하는 흐름을 눈으로 보기
for문이 한 바퀴 돌 때(한 사람을 처리할 때) 세 부품이 어떻게 연쇄로 불리는지 그림으로 보면 이래요.
printRecommendations 의 for문 1회전 (사용자 한 명)
│
│ ① calculateRecommendScore(팔로워, 게시물, 친구, 활동)
▼
점수 357 계산 ─────────────┐
│ │
│ ② classifyScore(357) │ ③ formatFollowers(8500)
▼ ▼
"강력 추천" "8.5K"
│ │
└──────────┬───────────────┘
▼
"@minji_cafe (팔로워 8.5K) 점수 357점 → 강력 추천" ← 한 줄 완성!
한 사람이 들어가면 점수 계산 → 등급 분류 → 팔로워 포맷을 거쳐 완성된 한 줄이 튀어나와요.
이 흐름이 사용자 수(여섯 번)만큼 반복되면서 추천 목록 전체가 화면에 찍히는 거죠.
printRecommendations는 직접 점수를 계산하지도, 등급을 나누지도 않아요.
그저 "누가 그 일을 잘하는지" 알고 그 부품들을 부르기만 할 뿐이에요.
main에서 부르는 모습
이렇게 종합 메서드를 만들어두면, main에서는 또 딱 한 줄이면 끝나요.
System.out.println("=== 추천 점수 & 등급 ===");
printRecommendations(usernames, followers, posts, mutualFriends, daysActive);
복잡한 for문도, 세 부품을 부르는 절차도 전부 printRecommendations 안으로 들어갔어요.
main은 그냥 "추천 목록 보여줘" 하고 지시만 하면 됩니다.
Step 2의 printAllUsers를 부를 때와 똑같은 모습이죠?
실행하면 이렇게 나와요
이 종합 메서드까지 만들고 실행하면, 사용자 여섯 명 전원의 점수와 등급이 한 표로 쭉 나와요.
=== 추천 점수 & 등급 ===
@jaehoon_dev (팔로워 1.2K) 점수 104점 → 보통
@minji_cafe (팔로워 8.5K) 점수 357점 → 강력 추천
@seungwoo (팔로워 320) 점수 26점 → 관심 낮음
@soyeon_art (팔로워 4.1K) 점수 215점 → 추천
@wooseok99 (팔로워 15.8K) 점수 638점 → 강력 추천
@hayoung_food (팔로워 2.3K) 점수 149점 → 보통
한 사람 한 사람 점수가 다르게 계산되고, 그 점수에 맞는 등급이 붙은 게 보이시죠?
seungwoo는 팔로워도 적고 활동도 짧아서 26점, "관심 낮음"이 됐고요.
wooseok99는 팔로워 15800명에 활동도 길어서 638점, "강력 추천"이 됐어요.
점수 계산(calculateRecommendScore)·등급 분류(classifyScore)·팔로워 포맷(formatFollowers)이 사람마다 알아서 일한 결과가 이 한 표예요.
💡 튜터의 결론
작은 부품을 잘 나눠두면, 큰 기능은 그 부품들을 부르기만 하면 돼요.
printRecommendations가 점수 계산·등급 분류·팔로워 포맷을 직접 하지 않고 각 메서드에게 맡기듯, 책임을 잘게 나눌수록 종합하는 코드는 오히려 단순하고 읽기 쉬워집니다.
추천 목록까지 완성되면서 오늘 만들 분석기의 모든 기능이 갖춰졌어요. 마지막 Step에서는 전체 그림을 정리하고, 이 프로그램을 디버거로 천천히 들여다보겠습니다.
Step 7: "완성한 프로그램을 디버거로 천천히 들여다보기"
축하해요! 방금 Step 6에서 오늘 만들 기능을 전부 완성했어요. 이번 마지막 Step에서는 두 가지를 할 거예요. 먼저 지금까지 만든 부품들이 한 프로그램으로 어떻게 맞물리는지 전체 그림을 정리하고요, 그다음 그 프로그램이 실제로 어떻게 한 줄씩 실행되는지 디버거라는 도구로 직접 들여다봅니다.
(1) 우리가 만든 부품들의 전체 그림
오늘 우리는 메서드를 일곱 개나 만들었어요.
하나씩 보면 작은 부품이지만, main을 정점으로 모이면 하나의 프로그램이 됩니다.
어떤 메서드가 어떤 메서드를 부르는지 호출 지도로 그려볼게요.
main
│
┌──────────┬───────┴────────┬─────────────────┐
│ │ │ │
printAllUsers findTopFollowers searchUserByName printRecommendations
│ │
│ formatFollowers ├─ calculateRecommendScore
▼ ├─ classifyScore
formatFollowers └─ formatFollowers
main은 직접 일을 하지 않아요. 상황에 따라 알맞은 메서드를 골라 부르는 지휘자 역할만 해요.
검색어가 없으면 printAllUsers·findTopFollowers·printRecommendations를 부르고,
검색어가 있으면 searchUserByName으로 한 명을 찾죠.
그리고 그 아래 메서드들은 또 자기보다 작은 메서드(formatFollowers, calculateRecommendScore, classifyScore)를 불러요.
큰 부품이 작은 부품을 부르고, 그게 또 더 작은 부품을 부르는 이 구조가 바로 메서드 합성의 전체 그림이에요.
오늘 만든 부품들을 한눈에 정리하면 이렇습니다.
| 메서드 | 한 줄 역할 |
|---|---|
formatFollowers |
팔로워 숫자를 1.2K처럼 보기 좋게 다듬어요 |
printAllUsers |
전체 사용자를 한 명씩 한 줄로 출력해요 |
searchUserByName |
이름으로 사용자를 찾아 인덱스를 돌려줘요 (없으면 -1) |
findTopFollowers |
팔로워 상위 N명의 인덱스를 골라내요 |
calculateRecommendScore |
네 가지 정보를 가중합해 추천 점수를 계산해요 |
classifyScore |
점수를 "강력 추천" 같은 등급 글자로 바꿔요 |
printRecommendations |
위 부품들을 모아 추천 목록 한 표를 완성해요 |
이 표를 보면, 메서드 하나하나가 딱 한 가지 일만 맡고 있는 게 보이시죠?
이렇게 작은 일로 잘게 나눠두니까, main은 그저 이 부품들을 조립하기만 하면 됐어요.
(2) IntelliJ 디버거로 실행 흐름 따라가기
이제 오늘의 마지막 도구를 만나봅시다. 바로 디버거(debugger) 예요. 디버거를 우리말로 풀면 "버그(오류)를 잡아주는 도구"라는 뜻인데, 사실 버그를 잡는 것뿐 아니라 프로그램이 실행되는 도중의 모습을 천천히 들여다볼 때 아주 강력해요.
지난 시간에 "에러는 친구"라고 했던 거 기억나시죠? 에러 메시지를 읽으며 친해지는 연습을 했는데, 디버거는 거기서 한 걸음 더 나아가요. 에러가 나기 전에, 프로그램이 돌아가는 한복판에서 "지금 변수에 어떤 값이 들어 있나"를 직접 눈으로 볼 수 있게 해주거든요.
왜 디버거를 쓸까?
지금까지 우리는 코드가 잘 도는지 확인할 때 System.out.println으로 값을 찍어봤어요.
"여기서 score가 얼마지?" 궁금하면 System.out.println(score)를 한 줄 끼워 넣고 실행했죠.
나쁘진 않지만, 궁금한 변수가 늘어날 때마다 출력문을 계속 끼워 넣고 다시 지워야 해서 번거로워요.
디버거를 쓰면 그럴 필요가 없어요. 프로그램을 잠깐 멈춰 세우고, 그 순간 살아 있는 모든 변수의 값을 한 화면에서 통째로 볼 수 있거든요. 출력문을 끼워 넣지 않고도요. 그래서 코드의 흐름을 이해하거나 버그를 찾을 때 정말 든든한 친구가 돼요.
디버거를 쓸 때 알아야 할 도구는 딱 세 가지예요.
도구 1: Breakpoint(중단점) — "여기서 멈춰!"
먼저 Breakpoint, 우리말로 중단점이에요. 프로그램이 "이 줄에 도착하면 잠깐 멈춰라" 하고 표시해두는 지점이에요.
IntelliJ에서는 멈추고 싶은 줄의 왼쪽, 줄 번호 바로 옆 빈 공간을 한 번 클릭하면 돼요. 그러면 거기에 빨간 동그란 점이 찍혀요. 그게 바로 중단점이에요. 이 상태에서 프로그램을 (Run이 아니라) Debug 모드로 실행하면, 프로그램이 쭉 돌다가 그 빨간 점이 찍힌 줄에 도착하는 순간 딱 멈춰 섭니다.
예를 들어 printRecommendations 안에서 점수를 계산하는 줄에 중단점을 찍어두면,
사용자 한 명을 처리할 때마다 그 줄에서 멈춰서 "지금 이 사람의 점수가 얼마인지" 확인할 수 있어요.
도구 2: Step Over(한 줄씩 실행, F8) — "다음 한 줄로"
프로그램이 중단점에서 멈췄으면, 이제 한 줄씩 천천히 진행시킬 차례예요. 이때 쓰는 게 Step Over, 단축키는 F8이에요.
F8을 한 번 누르면 멈춰 있던 줄이 실행되고, 바로 다음 한 줄로 넘어가서 다시 멈춰요. 또 누르면 또 한 줄 실행하고 멈추고요. 마치 영상을 한 프레임씩 넘겨 보듯이, 코드가 위에서 아래로 한 줄씩 실행되는 모습을 천천히 따라갈 수 있어요.
for문 안에 멈춰 있다면, F8을 누를 때마다 반복문이 한 줄씩 진행되다가
한 바퀴를 다 돌면 다시 for문 맨 위로 올라가 다음 사람을 처리하는 모습도 볼 수 있어요.
도구 3: Variables 창 — "지금 값이 다 보여요"
마지막은 Variables 창이에요. 디버거의 진짜 매력이 여기 있어요. 프로그램이 중단점에서 멈춘 그 순간, 살아 있는 모든 변수와 배열의 값이 이 창에 실시간으로 나타나요.
i가 지금 몇인지, score에 어떤 값이 담겼는지, grade가 무슨 글자인지가 죽 펼쳐져요.
배열도 펼쳐서 안에 든 값을 하나하나 볼 수 있고요.
F8로 한 줄씩 넘길 때마다 이 값들이 눈앞에서 바뀌는 게 보여요.
디버거 화면은 이렇게 생겼어요
IntelliJ에서 Debug 모드로 실행하면 화면이 대략 이런 모습이 돼요.
┌─────────────────────────────────────────────────┐
│ InstagramUserManager.java │
│ │
│ 134 for (int i = 0; i < usernames...) { │
│ ● 135 int score = calculateReco... │ ← 빨간 점(●) = 중단점
│ 136 String grade = classifyScore... │ ← 노란 줄 = 지금 멈춘 위치
│ 137 System.out.println(...); │
│ │
├─────────────────────────────────────────────────┤
│ Variables │
│ i = 1 │
│ score = 357 │
│ grade = "강력 추천" │
│ usernames = {"jaehoon_dev", "minji_cafe", ..} │
└─────────────────────────────────────────────────┘
위쪽엔 코드가 보이고, 빨간 점이 찍힌 줄에서 프로그램이 멈춰 있어요.
아래쪽 Variables 창엔 그 순간의 변수 값(i, score, grade...)이 쭉 펼쳐져 있고요.
F8을 누르면 노란 줄(지금 실행 위치)이 한 칸 내려가고, Variables 창의 값도 따라 바뀌어요.
추천 실습: 순위 뽑는 과정을 눈으로 보기
오늘 만든 것 중에 디버거로 보면 특히 재밌는 게 하나 있어요. 바로 findTopFollowers예요.
이 메서드 안에서 maxIndex와 used 배열이 회전마다 어떻게 바뀌는지 글로만 따라갔는데,
디버거로 보면 그게 눈앞에서 실시간으로 움직여요.
findTopFollowers 안쪽 for문에 중단점을 찍고 Debug 모드로 실행한 뒤 F8을 눌러보세요.
Variables 창에서 이런 변화를 직접 관찰할 수 있어요.
maxIndex가 처음엔-1이었다가, 더 큰 팔로워를 만날 때마다 그 위치로 바뀌는 모습used배열이 처음엔 전부false였다가, 한 명을 뽑을 때마다 그 칸이true로 바뀌는 모습- 바깥
for문이 한 바퀴 돌 때마다result배열에 순위 인덱스가 하나씩 채워지는 모습
Step 3에서 그림으로 봤던 "한 명씩 골라내는 과정"이, 이번엔 진짜 값으로 움직이는 걸 보게 되는 거예요.
또는 printRecommendations의 for문에 중단점을 찍어두면, 사람이 바뀔 때마다 score와 grade가 다른 값으로 갱신되는 것도 관찰할 수 있어요.
🙋 학생 질문 — "튜터님, 디버거를 꼭 써야 하나요? print로도 충분하던데요."
지금처럼 코드가 짧을 때는 System.out.println으로도 충분히 값을 확인할 수 있어요.
디버거를 꼭 써야 하는 건 아니에요.
다만 코드가 점점 길어지고 복잡해지면 이야기가 달라져요. 변수가 수십 개씩 얽혀 있을 때 그걸 전부 출력문으로 찍으려면 코드가 출력문으로 도배되거든요. 게다가 찍어보고 나면 그 출력문을 다시 지워야 하고요.
디버거는 출력문을 한 줄도 안 넣고, 멈춘 순간의 모든 값을 한 화면에서 보여줘요. 지금은 "이런 도구가 있구나, 한 줄씩 멈춰 보니 흐름이 눈에 들어오네" 정도만 경험해두면 충분해요. 나중에 진짜 까다로운 버그를 만났을 때, 오늘 만져본 이 도구가 큰 힘이 될 거예요.
💡 튜터의 결론
디버거는 중단점(Breakpoint)으로 원하는 줄에서 프로그램을 멈추고, F8(Step Over)로 한 줄씩 진행하며, Variables 창에서 그 순간의 모든 변숫값을 눈으로 봅니다.
System.out.println을 일일이 끼워 넣지 않고도 실행 도중의 흐름을 통째로 들여다볼 수 있어, 버그를 잡고 코드를 이해하는 든든한 친구가 됩니다.
자, 이렇게 오늘 만든 분석기를 처음부터 끝까지 디버거로 따라가 봤어요. 변수, 배열, 조건문, 반복문, 메서드 — Day 1부터 모은 도구를 전부 꺼내 작동하는 프로그램 하나를 직접 조립하고, 그게 실행되는 모습까지 눈으로 확인했네요. 정말 큰 산을 하나 넘었어요!
마무리
오늘은 새 문법을 외우는 날이 아니라, 그동안 모은 도구를 한 프로젝트에 전부 꺼내 쓰는 날이었어요. 오늘 걸어온 길을 한 줄씩 되짚어 볼게요.
- 완성품 먼저 구경 — 코드를 짜기 전에 "전체 목록 / 검색 / 상위 N명 / 추천 등급" 네 기능이 실제로 어떤 화면으로 나오는지 미리 봤어요
- 조회 메서드 —
formatFollowers로 팔로워 숫자를 예쁘게 다듬고,printAllUsers로 전체를 한 줄씩 출력하면서 메서드가 메서드를 부르는 모습을 처음 봤어요 - 검색과 정렬 — 이름으로 찾아 인덱스를 돌려주는
searchUserByName(못 찾으면 -1), 그리고 평행 배열이라 통째로 정렬을 못 쓰니 "가장 큰 값을 N번 골라내는"findTopFollowers를 직접 만들었어요 args활용 — 프로그램을 실행할 때 외부에서 검색어를 건네받는 통로가main의String[] args였다는 걸 확인하고, 검색어가 있느냐 없느냐로 갈림길을 만들었어요- 점수 계산과 등급 분류 — 네 가지 정보를 가중합으로 섞는
calculateRecommendScore, 그 점수를 "강력 추천 / 추천 / 보통" 글자로 바꾸는classifyScore를 만들었어요 - 추천 목록 완성 —
printRecommendations한 메서드 안에서 점수 계산 → 등급 분류 → 숫자 포맷팅을 줄줄이 불러, 작은 부품들이 한 표로 합쳐지는 클라이맥스를 봤어요 - 디버거로 들여다보기 — 중단점·F8·Variables 창으로 프로그램이 돌아가는 한복판을 멈춰 세우고, 변수 값이 한 줄씩 바뀌는 모습을 눈으로 따라갔어요
오늘의 핵심을 한 줄로 누르면 이거예요.
지금까지 배운 변수·배열·조건문·반복문·메서드를 한 프로젝트에 전부 조립했고, 거기에 외부 입력을 받는 args 와 실행을 들여다보는 디버거라는 새 도구 두 개를 더했다.
새로 외운 문법은 거의 없었지만, 흩어져 있던 도구들이 하나로 맞물려 "작동하는 프로그램"이 된 게 오늘의 진짜 수확이에요.
그런데 오늘 내내 발끝에 걸렸던 돌부리 하나, 느끼셨나요?
바로 그 평행 배열 다섯 개예요. usernames, followers, posts, mutualFriends, daysActive — 이 다섯이 항상 한 몸처럼 같이 다녔죠. 메서드를 하나 부를 때마다 다섯 개를 줄줄이 인자로 넘겨야 했고, printRecommendations 의 매개변수는 무려 다섯 개까지 늘어났어요. 사용자 한 명에 정보를 하나만 더 추가하려고 해도, 배열을 새로 만들고, 모든 메서드의 매개변수를 고치고, 호출하는 자리도 전부 손봐야 했고요.
그리고 이 다섯 배열은 같은 인덱스가 한 사람 이라는 약속 위에서만 동작해요. 만약 한 배열의 순서만 어긋나면? 재훈이의 팔로워가 민지의 게시물 수와 짝지어지는 끔찍한 일이 벌어지죠. 누구도 강제해주지 않는, 우리 머릿속에만 있는 위태로운 약속이에요.
자연스럽게 이런 생각이 들지 않나요?
"사용자 한 명에 관한 정보(이름, 팔로워, 게시물, 친구, 활동 일수)를 하나로 묶어둘 방법은 없을까? 그러면 배열도 하나면 되고, 메서드에 넘길 것도 '사용자 한 명'이면 끝일 텐데."
바로 그 답을 다음 시간(Day 8)에 준비해 뒀어요. 흩어진 다섯 조각의 정보를 하나의 설계도로 묶는 방법을 배웁니다. 붕어빵을 똑같은 모양으로 계속 찍어내는 그 틀 같은 거라고만 살짝 귀띔해 둘게요. 그게 뭔지는 다음 시간에 직접 만나보기로 해요.
오늘로 Phase 1(Day 1~7) 이 끝났어요. 코드 한 줄 못 쓰던 데서 시작해, 이제 여러분은 데이터를 담고·검색하고·정렬하고·점수를 매겨 등급을 붙이는 작동하는 프로그램 하나를 직접 만들 수 있게 됐습니다. 정말 멀리 왔어요. 다음 시간부터는 이 프로그램을 한 단계 더 단단하게 만드는 여행을 시작합니다.
과제
오늘 만든 InstagramUserManager 분석기를 직접 손보면서 익혀볼 시간이에요. 세 과제 모두 오늘의 분석기를 확장하거나 변형하는 거라, 처음부터 새로 짤 필요는 없어요. 기존 데이터와 메서드를 그대로 두고 거기에 붙이면 됩니다. 난이도별로 세 개 준비했어요.
과제 1: [기초] 전체 팔로워 합계와 평균 내기
오늘 Step 2 에서 배운 조회 메서드를 살짝 변형하는 과제예요. 분석기에 "전체 통계" 기능을 하나 더 붙여봅시다.
만들어야 할 메서드:
static void printFollowerStats(String[] usernames, int[] followers) — 등록된 사용자 전체의 팔로워 합계와 평균을 한 번에 출력한다.
요구사항:
for루프로followers배열을 한 바퀴 돌며 전부 더해서 합계를 구하세요.- 합계를 사용자 수(
followers.length)로 나눠 평균을 구하세요. - 출력은 팔로워 숫자에 오늘 만든
formatFollowers를 그대로 활용해서 예쁘게 보여주면 더 좋아요.
출력 예시:
=== 팔로워 통계 ===
전체 사용자: 6명
팔로워 합계: 32.2K
팔로워 평균: 5.3K
힌트:
- 합계를 담을
int total = 0;변수를 루프 밖에 하나 두고, 루프 안에서total = total + followers[i];로 쌓아가면 돼요. - 평균은
int average = total / followers.length;로 구하면 됩니다. 정수 나눗셈이라 소수점은 버려지는데, 지금은 그걸로 충분해요.
과제 2: [응용] "강력 추천" 사용자만 골라 출력하기
오늘 Step 5(점수 계산·등급 분류)와 Step 6(메서드 합성)을 함께 쓰는 과제예요. 추천 목록 전체가 아니라, 등급이 특정 조건을 넘는 사람만 걸러서 보여주는 기능을 만들어 봅시다.
만들어야 할 메서드:
static void printOnlyStrongRecommendations(String[] usernames, int[] followers, int[] posts, int[] mutualFriends, int[] daysActive) — 추천 등급이 "강력 추천" 인 사용자만 골라 출력한다.
요구사항:
- 전체 사용자를
for루프로 한 바퀴 돌면서, 각 사람의 점수를 오늘 만든calculateRecommendScore로 계산하세요. - 그 점수를
classifyScore에 넘겨 등급 글자를 받으세요. - 등급이
"강력 추천"일 때만 그 사람을 출력하세요. (조건 분기로 걸러내기) - 한 명도 해당되지 않으면
"강력 추천 사용자가 없어요."한 줄을 출력해 주세요.
출력 예시:
=== 강력 추천 사용자 ===
@minji_cafe (팔로워 8.5K) 점수 357점
@wooseok99 (팔로워 15.8K) 점수 ...점
힌트:
- 등급 글자를 비교할 때는
if (grade.equals("강력 추천"))처럼.equals를 써야 해요. (문자열 비교는==가 아니라.equals! Day 2 에서 짚었던 부분이에요.) - "한 명이라도 출력했는지" 를 기록하는
boolean found = false;변수를 두면, 마지막에 아무도 없었을 때 안내 문구를 띄우기 편해요.
과제 3: [심화] 검색어를 여러 개 받아 한꺼번에 조회하기
오늘 Step 3(검색)과 Step 4(args 활용)를 종합하는 과제예요. 오늘의 마지막 미션입니다. 지금 분석기는 검색어를 args[0] 한 개만 처리했는데, 이걸 여러 개 받을 수 있게 확장해 봅시다.
문제 상황:
지금은 프로그램을 실행할 때 이름을 하나만 건넬 수 있어요 (args[0]). 그런데 "재훈이랑 민지랑 우석이 정보를 한 번에 보고 싶다"면? 매번 프로그램을 세 번 실행하긴 번거롭죠. 실행할 때 이름을 여러 개 건네면 그걸 전부 찾아주도록 만들어 봅시다.
요구사항:
args에 이름이 여러 개 들어올 수 있으니 (jaehoon_dev minji_cafe wooseok99),args배열을for루프로 한 바퀴 돌며 하나씩 검색하세요.- 각 검색어마다 오늘 만든
searchUserByName으로 인덱스를 찾고, 찾았으면 그 사람의 상세 정보(팔로워·게시물·점수·등급)를 출력하세요. - 못 찾은 검색어는
"'xxx' 사용자를 찾을 수 없어요."한 줄로 넘어가세요. args가 아예 비어 있으면 (검색어 없음) 기존처럼 전체 목록을 보여주면 돼요.
실행 예시 (IntelliJ 의 Program arguments 에 jaehoon_dev nobody wooseok99 를 넣고 실행):
=== @jaehoon_dev 상세 정보 ===
팔로워 : 1.2K
...
추천 점수 : ...점 → ...
'nobody' 사용자를 찾을 수 없어요.
=== @wooseok99 상세 정보 ===
...
힌트:
- 바깥
for루프가args를 돌고(for (int a = 0; a < args.length; a++)), 그 안에서searchUserByName(usernames, args[a])를 부르면 됩니다. 루프 안에서 메서드를 부르는 패턴이에요. - 한 사람의 상세 정보를 출력하는 부분을 별도 메서드(
printUserDetail같은)로 빼두면, 검색 성공할 때마다 그 메서드 한 줄만 부르면 돼서 코드가 훨씬 깔끔해져요. 메서드 합성을 한 번 더 연습하는 셈이에요.
도전 (선택): 검색한 사람들 중에서 누가 점수가 제일 높았는지도 마지막에 한 줄로 알려줘 보세요. 이걸 만들다 보면 "찾은 사람 정보를 따로 모아두려는데, 평행 배열을 또 새로 만들어야 하네?" 하는 답답함을 한 번 더 만나게 될 거예요. 그 답답함, 잘 기억해 두세요.
생각해볼 주제
오늘 만든 분석기 너머의 이야기를 세 가지 던져드릴게요. 정답이 정해진 질문이 아니라, 직접 코드를 떠올리며 본인의 답을 만들어보세요.
1. "평행 배열 다섯 개를 계속 끌고 다니는 게 왜 위험할까?"
오늘 우리는 usernames, followers, posts, mutualFriends, daysActive 다섯 배열을 항상 한 몸처럼 같이 다뤘어요. 메서드를 부를 때마다 다섯 개를 줄줄이 넘겼고요.
여기에 사용자 정보를 딱 하나만 더 추가한다고 상상해 보세요. 예를 들어 "프로필이 공개인지 비공개인지"요. 그러면 코드의 몇 군데를 고쳐야 할까요? 새 배열을 만들고, 그 배열을 받는 메서드들의 매개변수를 늘리고, 호출하는 자리도 전부 손봐야 하죠.
또 한 가지. 이 다섯 배열은 "같은 인덱스가 한 사람"이라는 약속 위에서만 맞아떨어져요. 만약 어느 한 배열의 순서만 살짝 어긋나면 어떤 일이 벌어질까요? 그리고 그 어긋남을, 컴파일러가 미리 잡아줄 수 있을까요?
흩어진 정보를 하나로 묶어둘 방법이 있다면 이 위험이 어떻게 줄어들지, 본인 나름대로 그려보세요.
2. "메서드를 어디까지 잘게 쪼개야 할까?"
오늘 우리는 팔로워 숫자를 "1.2K" 처럼 다듬는 일을 formatFollowers 라는 별도 메서드로 뺐어요. 사실 이건 코드 두세 줄짜리, 아주 작은 작업이에요. 이렇게 작은 것까지 메서드로 뺄 가치가 있었을까요?
반대로 생각해 봅시다. 만약 모든 걸 메서드로 쪼개서 한 줄짜리 메서드가 수십 개 생긴다면? 코드를 읽을 때 "이 메서드는 또 뭐 하는 거지?" 하고 정의를 찾아 위아래로 계속 뛰어다녀야 할 수도 있어요.
너무 큰 메서드도 읽기 힘들고, 너무 잘게 쪼갠 메서드도 읽기 힘들다면 — 그 사이 어딘가에 적당한 지점이 있을 거예요. 본인이라면 "이건 메서드로 빼자 / 이건 그냥 두자"를 무슨 기준으로 판단할지 생각해 보세요.
3. "-1 같은 '특별한 값'으로 결과를 표현하는 게 안전할까?"
오늘 searchUserByName 은 사용자를 못 찾으면 -1 을 돌려줬어요. 인덱스는 0부터 시작하니까 "절대 인덱스가 될 수 없는 값"인 -1 을 "못 찾았다"는 신호로 쓴 거죠. 아주 흔한 관례예요.
그런데 이 방식이 항상 안전할까요? 만약 어떤 메서드가 점수를 돌려주는데, "계산 실패"를 -1 로 표현한다고 해봐요. 그런데 점수가 진짜로 -1 이 나올 수 있는 상황이라면? -1 이 "실패"인지 "진짜 점수 -1"인지 구분할 수 없게 되죠.
"못 찾음 / 실패 / 없음" 같은 상태를 숫자 하나로 표현하는 게 언제 괜찮고 언제 위험한지, 그리고 호출하는 쪽이 -1 검사를 깜빡하면 무슨 일이 벌어질지 한번 떠올려 보세요.
✅ 예시 답안정답 보기
본인 답안을 먼저 작성한 뒤 비교해보세요. 정답이 하나만 있는 건 아니에요. 여기 답안은 모범 사례 중 하나일 뿐, 본인만의 더 깔끔한 풀이가 있다면 그게 답입니다.
세 과제 모두 오늘 만든 InstagramUserManager 의 데이터와 메서드를 그대로 재사용해요. 새 메서드만 하나씩 붙여서, main 에서 불러주면 됩니다.
🎯 [과제 1 예시 답안] 전체 팔로워 합계와 평균 내기
등록된 사용자 전체의 팔로워를 한 바퀴 돌며 더하고, 평균까지 한 번에 출력하는 printFollowerStats 메서드를 만드는 과제입니다.
핵심 접근
합계를 담을 변수 total 을 루프 밖에 하나 두고, for 루프 안에서 followers[i] 를 차곡차곡 더해요. 평균은 합계를 사용자 수(followers.length)로 나누면 됩니다. 출력할 때 숫자를 그대로 찍지 말고 오늘 만든 formatFollowers 를 재사용하면, 이미 만든 부품을 또 쓰는 메서드 합성의 맛을 한 번 더 느낄 수 있어요.
예시 코드
// day07/InstagramUserManager.java 에 메서드 하나 추가
// (main 에서 printFollowerStats(usernames, followers); 를 불러주면 동작해요)
static void printFollowerStats(String[] usernames, int[] followers) {
int total = 0;
for (int i = 0; i < followers.length; i++) {
total = total + followers[i];
}
int average = total / followers.length;
System.out.println("=== 팔로워 통계 ===");
System.out.println("전체 사용자: " + usernames.length + "명");
System.out.println("팔로워 합계: " + formatFollowers(total));
System.out.println("팔로워 평균: " + formatFollowers(average));
}
실행 결과:
=== 팔로워 통계 ===
전체 사용자: 6명
팔로워 합계: 32.2K
팔로워 평균: 5.3K
합계는 1240 + 8500 + 320 + 4100 + 15800 + 2300 = 32260 이고, 평균은 32260 / 6 = 5376 이에요. formatFollowers 는 소수점 아래를 버리는 방식이라, 32260 은 사실 32.26K 인데 화면엔 "32.2K" 로(끝자리 버림), 5376 은 5.376K 인데 "5.3K" 로 나옵니다. 반올림이 아니라 버림이라는 점만 기억해 두면 돼요.
채점 포인트
| 포인트 | 설명 | 배점 가중 |
|---|---|---|
| 합산 누적 패턴 | total 을 루프 밖에 두고 루프 안에서 total = total + followers[i] 로 쌓아감 |
상 |
| 평균 계산 | followers.length 로 나눠 평균을 구함 (배열 길이를 직접 활용) |
상 |
formatFollowers 재사용 |
숫자를 raw 로 찍지 않고 오늘 만든 메서드를 호출 | 중 |
| 출력 포맷 | 헤더 + 합계 + 평균 세 줄을 일관되게 정리 | 중 |
main 호출 한 줄 |
통계 로직을 메서드로 빼고 main 에서는 호출만 |
중 |
흔한 실수
total을 루프 안에서 선언 —for안에int total = 0;을 두면 매 반복마다 0으로 초기화돼서 합계가 안 쌓여요. 누적 변수는 반드시 루프 밖에.- 평균을 실수로 기대 —
int average = total / followers.length;는 정수 나눗셈이라 소수점이 버려져요. 5376.x 같은 소수 값을 기대하면 어긋납니다. 지금 단계에선 정수 평균으로 충분하고, 소수점이 필요하면 다음 시간 이후double로 다루게 됩니다. followers.length대신 숫자 6을 직접 박기 — 사용자가 7명으로 늘어나면 평균이 틀려져요. 배열 길이를 직접 쓰는 습관이 안전합니다.
실무 개선 포인트 (심화)
- 빈 배열 방어 — 만약
followers.length가 0이면total / 0에서 나눗셈 예외가 터져요. 실무에선if (followers.length == 0) { ...안내 후 return; }한 줄로 막아두는 게 기본입니다. - 합계가 너무 커지면? — 사용자가 수백만 명이고 팔로워가 큰 값이면
int합계가 표현 한계를 넘을 수 있어요. 이럴 땐 더 큰 정수 타입(long)으로 받습니다. "이 숫자가 얼마나 커질 수 있나"를 먼저 가늠하는 감각이 실무에선 중요해요.
🎯 [과제 2 예시 답안] "강력 추천" 사용자만 골라 출력하기
전체 사용자를 돌면서 점수를 계산하고, 등급이 "강력 추천" 인 사람만 걸러서 출력하는 과제입니다. 아무도 없을 때를 대비한 안내까지 챙기는 게 포인트예요.
핵심 접근
오늘 만든 calculateRecommendScore 로 점수를 구하고, 그 점수를 classifyScore 에 넘겨 등급 글자를 받아요. 등급이 "강력 추천" 인지 비교할 때는 문자열이라 == 가 아니라 .equals 를 써야 합니다. 한 명이라도 출력했는지를 boolean found 로 기록해 두면, 루프가 끝난 뒤 "아무도 없었다" 안내를 띄울지 말지 깔끔하게 판단할 수 있어요.
예시 코드
// day07/InstagramUserManager.java 에 메서드 추가
// (main 에서 printOnlyStrongRecommendations(usernames, followers, posts, mutualFriends, daysActive); 호출)
static void printOnlyStrongRecommendations(String[] usernames, int[] followers,
int[] posts, int[] mutualFriends, int[] daysActive) {
System.out.println("=== 강력 추천 사용자 ===");
boolean found = false;
for (int i = 0; i < usernames.length; i++) {
int score = calculateRecommendScore(followers[i], posts[i],
mutualFriends[i], daysActive[i]);
String grade = classifyScore(score);
if (grade.equals("강력 추천")) {
System.out.println("@" + usernames[i]
+ " (팔로워 " + formatFollowers(followers[i]) + ")"
+ " 점수 " + score + "점");
found = true;
}
}
if (!found) {
System.out.println("강력 추천 사용자가 없어요.");
}
}
실행 결과:
=== 강력 추천 사용자 ===
@minji_cafe (팔로워 8.5K) 점수 357점
@wooseok99 (팔로워 15.8K) 점수 638점
점수가 300점 이상이면 "강력 추천" 이에요. minji_cafe 는 85 + 30 + 230 + 12 = 357점, wooseok99 는 158 + 64 + 400 + 16 = 638점이라 둘 다 통과합니다. 나머지 네 명은 300점에 못 미쳐서 걸러져요.
채점 포인트
| 포인트 | 설명 | 배점 가중 |
|---|---|---|
| 메서드 합성 호출 | calculateRecommendScore → classifyScore 를 루프 안에서 줄줄이 연결 |
상 |
문자열 비교 .equals |
등급을 == 가 아니라 grade.equals("강력 추천") 으로 비교 |
상 |
| 조건 분기 필터링 | "강력 추천" 일 때만 출력 (나머지는 건너뜀) | 상 |
boolean found 처리 |
한 명도 없을 때 안내 문구를 띄우는 분기 | 중 |
| 출력 포맷 | 등급 목록과 같은 형식으로 일관되게 출력 | 하 |
흔한 실수
grade == "강력 추천"— 가장 흔한 함정이에요. 문자열은==로 비교하면 "내용이 같은가" 가 아니라 "같은 객체인가" 를 보기 때문에, 같아 보여도false가 나오는 경우가 있어요. 반드시.equals를 써야 합니다. (Day 2 에서 짚었던 부분이에요.)found없이 처리 — 강력 추천이 한 명도 없을 때 아무것도 출력 안 하고 끝나면, 사용자는 "이 기능이 동작은 한 건가?" 하고 헷갈려요.boolean found로 "아무도 없었음" 안내를 챙기는 게 친절한 프로그램입니다.- 점수 300 기준을 직접 비교 —
if (score >= 300)으로 직접 거를 수도 있지만, 그러면 등급 기준이classifyScore와 이 메서드 두 군데에 흩어져요. 등급 글자로 비교하면 기준이 한 곳(classifyScore)에만 모입니다.
실무 개선 포인트 (심화)
- 등급 글자 비교의 약점 — "강력 추천" 이라는 글자를 코드 여기저기에 그대로 적으면, 나중에 등급 이름을 "최우선 추천" 으로 바꿀 때 모든 자리를 다 찾아 고쳐야 해요. 한 군데서 오타가 나면 조용히 안 걸러지고요. 이런 "정해진 몇 가지 값" 을 안전하게 다루는 방법은 다음 시간 이후 단원에서 다룹니다.
- 걸러낸 결과를 다시 쓰고 싶다면 — 지금은 찾는 즉시 출력만 하지만, "강력 추천 명단을 따로 모아 나중에 또 쓰고 싶다" 면 또 새 배열이 필요해져요. 과제 3의 도전 과제에서 이 답답함을 다시 만나게 됩니다.
🎯 [과제 3 예시 답안] 검색어를 여러 개 받아 한꺼번에 조회하기
args 로 들어온 이름을 하나씩 검색해서, 찾으면 상세 정보를 출력하고 못 찾으면 안내만 하는 과제입니다. 상세 출력 부분을 별도 메서드로 빼는 게 깔끔함의 핵심이에요.
핵심 접근
바깥 for 루프가 args 를 한 바퀴 돌고, 그 안에서 검색어 하나씩을 searchUserByName 으로 찾아요. 돌려받은 인덱스가 -1 이면 "못 찾음" 안내, 아니면 상세 정보를 출력합니다. 상세 출력은 줄이 길어서 printUserDetail 이라는 메서드로 빼두면, 검색에 성공할 때마다 한 줄로 부를 수 있어 main 이 훨씬 읽기 편해져요. args 가 아예 비었으면 기존처럼 전체 목록을 보여주면 됩니다.
예시 코드
먼저 상세 정보 출력을 메서드로 뺍니다. 기존 main 의 검색 분기에 흩어져 있던 출력 코드를 한곳에 모은 거예요.
// day07/InstagramUserManager.java 에 메서드 추가
// 한 사람의 상세 정보를 출력하는 메서드 — main 의 검색 분기에서 빼낸 부품
static void printUserDetail(String[] usernames, int[] followers, int[] posts,
int[] mutualFriends, int[] daysActive, int idx) {
int score = calculateRecommendScore(followers[idx], posts[idx],
mutualFriends[idx], daysActive[idx]);
System.out.println("=== @" + usernames[idx] + " 상세 정보 ===");
System.out.println("팔로워 : " + formatFollowers(followers[idx]));
System.out.println("게시물 : " + posts[idx] + "개");
System.out.println("함께 아는 친구: " + mutualFriends[idx] + "명");
System.out.println("활동 일수 : " + daysActive[idx] + "일");
System.out.println("추천 점수 : " + score + "점 → " + classifyScore(score));
}
그리고 main 의 검색 분기를 여러 개 처리하도록 바꿉니다.
// main 안의 else 블록 (검색어가 있을 때) 을 이렇게 확장
// 검색어가 여러 개 들어올 수 있으니 args 를 한 바퀴 돌아요
for (int a = 0; a < args.length; a++) {
String target = args[a];
int idx = searchUserByName(usernames, target);
if (idx == -1) {
System.out.println("'" + target + "' 사용자를 찾을 수 없어요.");
} else {
printUserDetail(usernames, followers, posts, mutualFriends, daysActive, idx);
}
}
IntelliJ 의 Program arguments 에 jaehoon_dev nobody wooseok99 를 넣고 실행하면:
=== @jaehoon_dev 상세 정보 ===
팔로워 : 1.2K
게시물 : 42개
함께 아는 친구: 8명
활동 일수 : 120일
추천 점수 : 90점 → 보통
'nobody' 사용자를 찾을 수 없어요.
=== @wooseok99 상세 정보 ===
팔로워 : 15.8K
게시물 : 320개
함께 아는 친구: 40명
활동 일수 : 500일
추천 점수 : 638점 → 강력 추천
채점 포인트
| 포인트 | 설명 | 배점 가중 |
|---|---|---|
args 루프 순회 |
바깥 for 가 args 를 돌며 검색어를 하나씩 처리 |
상 |
| 루프 안 메서드 호출 | 루프 안에서 searchUserByName 을 매번 호출 |
상 |
-1 검사 분기 |
못 찾았을 때(idx == -1)와 찾았을 때를 정확히 분기 |
상 |
| 상세 출력 메서드 추출 | 출력 코드를 printUserDetail 로 빼서 합성 |
중 |
빈 args 처리 |
검색어가 없으면 전체 목록으로 분기 (기존 동작 유지) | 중 |
흔한 실수
-1검사 누락 —searchUserByName이-1을 돌려줬는데 그대로usernames[-1]에 접근하면 배열 범위 예외가 터져요. 찾은 결과는 항상 "못 찾음(-1)" 부터 검사하는 습관이 필요합니다.args.length체크 없이args[0]접근 — 검색어가 없는데args[0]을 바로 읽으면 범위 예외예요. "비어 있으면 전체 목록" 분기를 먼저 두면 안전합니다.- 상세 출력을
main에 그대로 둠 — 동작은 하지만main이 길어지고, 검색 성공 자리마다 같은 6줄을 복사하게 돼요. 메서드로 빼면 한 줄 호출로 끝납니다. args변수명을i로 재사용 — 바깥 루프 변수와 안쪽 검색 루프 변수가 겹치면 헷갈려요. 바깥은a, 안쪽은i처럼 구분하면 읽기 편합니다.
실무 개선 포인트 (심화) — 도전 과제 "최고 점수 1명 추적"
검색된 사람들 중 점수가 제일 높은 한 명을 마지막에 알려주려면, "지금까지 본 최고 점수" 와 "그 사람의 인덱스" 두 값을 루프 밖에 들고 다녀야 해요.
// 도전 과제 — 검색 성공한 사람들 중 최고 점수 1명 추적
int bestScore = -1; // 아직 아무도 못 봤다는 표시
int bestIdx = -1;
for (int a = 0; a < args.length; a++) {
int idx = searchUserByName(usernames, args[a]);
if (idx == -1) {
System.out.println("'" + args[a] + "' 사용자를 찾을 수 없어요.");
continue;
}
printUserDetail(usernames, followers, posts, mutualFriends, daysActive, idx);
int score = calculateRecommendScore(followers[idx], posts[idx],
mutualFriends[idx], daysActive[idx]);
if (score > bestScore) {
bestScore = score;
bestIdx = idx;
}
}
if (bestIdx != -1) {
System.out.println();
System.out.println("이번에 검색한 사람 중 최고 점수: @"
+ usernames[bestIdx] + " (" + bestScore + "점)");
}
여기서 "최고 점수" 와 "그 사람 인덱스" 를 짝으로 들고 다녀야 한다는 게 느껴지셨나요? 점수 따로, 인덱스 따로 변수를 두 개 끌고 다니다 보면 — "한 사람을 가리키는 정보가 또 여러 변수로 흩어지네?" 하는 답답함을 만나게 돼요. 발제에서 말한 그 답답함이 바로 이거예요. 다음 단원에서 "흩어진 정보를 하나로 묶는" 방법을 배우면 이 부분이 훨씬 산뜻해집니다.
🤔 생각해볼 주제 예시답안
1. 평행 배열 다섯 개를 계속 끌고 다니는 게 왜 위험할까?
[문제 상황 요약]
오늘 우리는 usernames, followers, posts, mutualFriends, daysActive 다섯 배열을 항상 한 몸처럼 같이 다뤘어요. 메서드를 부를 때마다 다섯 개를 줄줄이 넘겼고요. 여기에 "프로필 공개 여부" 같은 정보를 딱 하나만 더 추가하면, 코드의 몇 군데를 고쳐야 할까요? 그리고 이 다섯 배열은 "같은 인덱스가 한 사람" 이라는 약속 위에서만 맞아떨어지는데, 그 약속이 깨지면 어떻게 될까요?
[튜터의 가이드 및 해설]
평행 배열의 진짜 위험은 두 가지예요. 첫째는 변경 비용, 둘째는 안전망의 부재입니다.
변경 비용부터 볼게요. 정보 하나를 추가하면 새 배열 boolean[] isPrivate 을 만들고, 그 배열을 받아야 하는 메서드들의 매개변수를 전부 늘리고, 호출하는 자리도 모두 손봐야 해요. 오늘 printRecommendations 가 매개변수를 다섯 개나 받는 걸 보셨죠? 정보가 하나 늘 때마다 이 매개변수 줄이 계속 길어집니다. 작은 변경 하나가 코드 전체를 건드리는 큰일이 되는 거예요.
더 무서운 건 두 번째예요. 다섯 배열은 "0번 인덱스는 jaehoon_dev, 1번은 minji_cafe" 라는 암묵적 약속에만 기대고 있어요. 만약 followers 배열만 실수로 정렬하거나 순서를 바꾸면, jaehoon_dev 의 팔로워 자리에 다른 사람 숫자가 들어가요. 그런데 컴파일러는 이걸 잡아주지 못해요. 다섯 개 다 int[] 와 String[] 일 뿐, "이 배열들이 같은 사람을 가리킨다" 는 정보가 코드 어디에도 적혀 있지 않으니까요. 에러 없이 조용히 잘못된 결과가 나오는 게 가장 위험한 버그입니다.
- Option A — 평행 배열 유지: 지금처럼 배열 다섯 개. 장점은 단순하고 지금 배운 문법만으로 충분하다는 것. 단점은 정보가 늘수록 변경 비용이 커지고, 인덱스 어긋남을 막을 안전망이 없다는 것.
- Option B — 흩어진 정보를 하나로 묶기: "한 사람" 에 속한 정보(이름·팔로워·게시물·친구·활동일)를 하나의 묶음으로 다루는 설계. 장점은 정보를 추가해도 묶음 한 곳만 고치면 되고, "같은 사람" 이라는 약속이 코드에 명시적으로 드러나 인덱스 어긋남이 원천적으로 사라진다는 것.
- 현업에서는 보통: 정보가 두세 개를 넘어가면 거의 항상 묶는 쪽을 택해요. 평행 배열은 정보가 한두 개일 때만 잠깐 쓰는 임시 도구에 가깝습니다.
이 "흩어진 정보를 하나로 묶는 설계" 가 다음 단원의 핵심 주제예요. 오늘 다섯 배열을 줄줄이 넘기며 느낀 답답함이, 바로 그 단원을 배우는 동기가 됩니다.
🎯 면접관을 홀리는 핵심 멘트
"평행 배열의 가장 큰 위험은 '같은 인덱스가 한 항목' 이라는 약속이 코드 어디에도 명시되지 않아 컴파일러가 어긋남을 잡아주지 못한다는 점입니다. 그래서 한 항목에 속한 데이터는 함께 묶어 응집도를 높이는 게 안전합니다. 데이터가 함께 움직여야 한다면 함께 정의되어야 하고, 그래야 정보를 추가할 때 한 곳만 고치면 되고 순서 어긋남 같은 조용한 버그도 사라집니다."
2. 메서드를 어디까지 잘게 쪼개야 할까?
[문제 상황 요약]
오늘 우리는 팔로워 숫자를 "1.2K" 로 다듬는 일을 formatFollowers 라는 별도 메서드로 뺐어요. 코드 두세 줄짜리 작은 작업인데 메서드로 뺄 가치가 있었을까요? 반대로, 모든 걸 한 줄짜리 메서드 수십 개로 쪼개면 정의를 찾아 위아래로 뛰어다녀야 할 수도 있어요. 너무 큰 것도 너무 잘게 쪼갠 것도 읽기 힘들다면, 그 사이 적당한 지점은 어디일까요?
[튜터의 가이드 및 해설]
메서드 분리의 기준은 "줄 수" 가 아니라 "역할" 이에요. 줄 수로 자르려고 하면 답이 안 나옵니다.
formatFollowers 를 보세요. 두세 줄짜리 작은 메서드지만 뺄 가치가 충분했어요. 이유는 세 가지예요. 첫째, 여러 곳에서 쓰여요. printAllUsers, printRecommendations, 상세 정보 출력에서 전부 이 메서드를 부릅니다. 만약 빼지 않았다면 "1000 이상이면 K로 바꾼다" 는 로직이 네다섯 군데에 복사됐을 거예요. 둘째, 이름이 의도를 설명해요. formatFollowers(8500) 이라고 적혀 있으면 "팔로워 숫자를 보기 좋게 다듬는구나" 가 한눈에 읽혀요. 셋째, 나중에 "1.2K 말고 1,240 으로 보여줘" 라고 바뀌면 이 메서드 한 곳만 고치면 됩니다.
판단 기준을 정리하면 이래요. 메서드로 빼는 게 좋은 신호는 — (1) 같은 코드가 두 번 이상 반복될 때, (2) 그 코드 덩어리에 좋은 이름을 붙일 수 있을 때, (3) 주석으로 "여기는 무엇무엇 하는 부분" 이라고 설명하고 싶어질 때(그 주석이 곧 메서드 이름이 됩니다). 반대로 굳이 안 빼도 되는 신호는 — 딱 한 곳에서만 쓰이고, 코드 자체가 이미 한눈에 읽히고, 좋은 이름이 안 떠오를 때예요.
- Option A — 잘게 많이 쪼개기: 장점은 각 메서드가 작아 테스트하기 쉽고 재사용이 늘어남. 단점은 한 줄짜리 메서드가 수십 개면 흐름을 따라가려 계속 점프해야 해서 오히려 읽기 어려워짐.
- Option B — 크게 뭉쳐두기: 장점은 한 메서드만 보면 전체 흐름이 보임. 단점은 길어지면 "이 메서드가 정확히 몇 가지 일을 하는지" 가 흐려지고, 일부만 재사용하기 어려움.
- 현업에서는 보통: "한 메서드는 한 가지 일만" 을 기준으로 삼아요. 메서드 이름을 한 문장으로 또렷하게 말할 수 있으면 적당한 크기, "이거랑 저거랑 그리고…" 하고 'and' 가 붙기 시작하면 너무 큰 거예요. 그 'and' 마다 쪼갤 후보가 됩니다.
🎯 면접관을 홀리는 핵심 멘트
"메서드 분리 기준은 줄 수가 아니라 단일 책임입니다. 메서드 이름을 'and' 없이 한 문장으로 말할 수 있으면 적당한 크기이고, 설명에 'and' 가 붙기 시작하면 쪼갤 신호로 봅니다. 두세 줄이라도 여러 곳에서 반복되거나 이름이 의도를 설명해 준다면 추출할 가치가 충분하고, 반대로 한 곳에서만 쓰이고 이미 한눈에 읽히는 코드는 굳이 쪼개 점프 비용을 늘리지 않습니다."
3. -1 같은 '특별한 값'으로 결과를 표현하는 게 안전할까?
[문제 상황 요약]
오늘 searchUserByName 은 사용자를 못 찾으면 -1 을 돌려줬어요. 인덱스는 0부터 시작하니까 "절대 인덱스가 될 수 없는 값" 인 -1 을 "못 찾음" 신호로 쓴 거죠. 아주 흔한 관례예요. 그런데 만약 어떤 메서드가 점수를 돌려주는데 "계산 실패" 를 -1 로 표현한다면, 그리고 점수가 진짜로 -1 이 나올 수 있다면? -1 이 "실패" 인지 "진짜 점수 -1" 인지 어떻게 구분할까요?
[튜터의 가이드 및 해설]
이런 "특별한 의미를 담은 값" 을 보통 sentinel value(파수꾼 값)라고 불러요. searchUserByName 의 -1 처럼요. 이게 안전한 경우와 위험한 경우를 가르는 기준이 딱 하나 있어요. "그 특별한 값이 정상 결과 범위와 절대 안 겹치는가?"
searchUserByName 은 안전한 쪽이에요. 인덱스는 항상 0 이상이라 -1 은 정상 결과로 절대 나올 수 없거든요. 그래서 -1 을 "못 찾음" 으로 쓰는 데 아무 문제가 없어요. 반대로 점수 계산에서 -1 을 "실패" 로 쓰는 건 위험해요. 점수가 진짜로 -1 일 수 있다면, 호출하는 쪽은 받은 -1 이 실패인지 진짜 점수인지 구분할 방법이 없어요. 정상 값과 특별 값이 겹치는 순간 sentinel 은 무너집니다.
두 번째 위험은 호출하는 쪽의 깜빡임이에요. sentinel 방식은 "받은 쪽이 반드시 검사해 줄 것" 이라는 믿음에 기대요. 만약 searchUserByName 의 결과를 -1 검사 없이 바로 usernames[idx] 에 넣으면? usernames[-1] 이 되어 배열 범위 예외가 터져요. 그런데 컴파일러는 "너 -1 검사 안 했어!" 라고 알려주지 않아요. 검사를 깜빡해도 컴파일은 멀쩡히 되고, 운 나쁘게 못 찾는 입력이 들어왔을 때야 비로소 터지는 거죠.
- Option A — sentinel value (
-1,null등): 장점은 단순하고 추가 타입이 필요 없음(지금 배운 문법으로 충분). 단점은 정상 값과 겹치면 무너지고, 호출자가 검사를 깜빡하면 조용히 사고로 이어짐. - Option B — "성공/실패" 를 결과 자체와 분리해 표현: 장점은 "값이 없을 수 있음" 을 타입 차원에서 강제해 호출자가 검사를 빼먹을 수 없게 함. 단점은 더 배워야 할 개념이 필요함(다음 단원 이후 주제).
- 현업에서는 보통: sentinel 은
-1처럼 "정상 범위와 절대 안 겹친다" 가 명백할 때만 제한적으로 써요. 그게 애매하면 "없을 수 있음" 을 명시적으로 드러내는 방식을 택합니다. 그래야 호출하는 쪽이 검사를 빼먹는 실수를 미리 막을 수 있거든요.
🎯 면접관을 홀리는 핵심 멘트
"sentinel value 는 그 특별한 값이 정상 결과 범위와 절대 겹치지 않을 때만 안전합니다. 인덱스의 -1 처럼요. 점수처럼 진짜로 -1 이 나올 수 있는 영역에서는 실패와 정상 값이 구분되지 않아 위험합니다. 더 근본적인 문제는 sentinel 이 '호출자가 반드시 검사한다' 는 믿음에 기댄다는 점입니다. 검사를 깜빡해도 컴파일러가 잡아주지 않으니, 값이 없을 수 있다는 사실을 타입으로 강제하는 방식이 호출자의 실수를 구조적으로 막아줍니다."