메서드와 중복 제거
메서드의 선언·호출·매개변수를 이해하고 배열 기반 Study Log의 검증과 계산을 작은 단위로 분리합니다.
코드가 한 번만 등장할 때는 main에 순서대로 적어도 읽을 수 있습니다.
같은 계산이나 검증이 두 번 이상 나타나면 한쪽만 수정되는 순간 결과가 갈라집니다.
메서드는 이름이 붙은 실행 단위입니다.
입력을 매개변수로 받고, 한 가지 책임을 수행하며, 호출한 곳으로 제어를 돌려줍니다.
중복 계산의 불일치
public final class DuplicatedSummaryBug {
public static void main(String[] args) {
int[] minutes = {40, 50, 30};
int size = 3;
int menuTotal = 0;
for (int index = 0; index < size; index++) {
menuTotal += minutes[index];
}
double menuAverage = (double) menuTotal / size;
System.out.println("menu=" + menuTotal + "/" + menuAverage);
int reportTotal = 0;
for (int index = 0; index < size - 1; index++) {
reportTotal += minutes[index];
}
double reportAverage = (double) reportTotal / size;
System.out.println("report=" + reportTotal + "/" + reportAverage);
}
}menu=120/40.0
report=90/30.0보고서 쪽 반복 조건이 size - 1로 달라져 마지막 기록을 빠뜨렸습니다.
눈앞의 한 줄만 고치면 되지만, 복사본이 열 개라면 열 군데를 찾아야 합니다.
합계 계산을 하나의 메서드로 모으면 두 호출이 같은 구현을 사용합니다.
private static int total(int[] minutes, int size) {
int result = 0;
for (int index = 0; index < size; index++) {
result += minutes[index];
}
return result;
}메서드가 존재한다고 자동으로 좋은 구조가 되는 것은 아닙니다.
process()처럼 의미가 흐린 이름에 모든 로직을 옮기면 main만 짧아질 뿐 복잡성은 이동합니다.
total, isValid, printEntries처럼 결과나 행동을 드러내는 이름을 사용합니다.
메서드 선언의 구성
private static int total(int[] minutes, int size) {
// 본문
return 0;
}private는 이 클래스 내부에서만 사용한다는 접근 범위입니다.static은static인main에서 객체 생성 없이 호출하게 합니다.int는 호출 결과의 반환 타입입니다.total은 호출할 때 사용하는 이름입니다.(int[] minutes, int size)는 입력을 받을 매개변수 선언입니다.
메서드 본문은 중괄호 안의 문장입니다.
선언만으로는 실행되지 않습니다.
int sum = total(minutes, size);처럼 호출해야 현재 실행 흐름이 메서드 본문으로 이동합니다.
본문이 끝나거나 return을 만나면 호출문 다음으로 돌아옵니다.
인수와 매개변수
호출문 total(minutes, size)의 minutes, size는 인수입니다.
선언부의 int[] minutes, int size는 인수를 받을 매개변수입니다.
위치와 타입이 대응합니다.
public final class MethodParameters {
public static void main(String[] args) {
int[] first = {20, 30};
int[] second = {40, 50, 60};
System.out.println(total(first, first.length));
System.out.println(total(second, 2));
}
private static int total(int[] values, int size) {
int result = 0;
for (int index = 0; index < size; index++) {
result += values[index];
}
return result;
}
}50
90같은 메서드가 서로 다른 배열과 범위에 동작합니다.
둘째 호출은 배열 길이가 3이어도 size로 2를 넘겼으므로 앞의 두 값만 더합니다.
메서드 내부가 특정 전역 배열을 몰래 읽지 않고 필요한 값을 매개변수로 받으면 테스트와 재사용이 쉬워집니다.
자바는 인수 값을 매개변수에 복사해 전달합니다.
기본형 int size는 숫자 값이 복사되므로 메서드 안에서 size를 증가시켜도 호출자의 지역 변수는 바뀌지 않습니다.
배열 매개변수에는 배열을 가리키는 참조 값이 복사되므로, 두 변수가 같은 배열을 가리키고 원소 변경은 보입니다.
import java.util.Arrays;
public final class ParameterEffects {
public static void main(String[] args) {
int size = 1;
int[] minutes = {40, 0};
change(size, minutes);
System.out.println("size=" + size);
System.out.println("minutes=" + Arrays.toString(minutes));
}
private static void change(int size, int[] minutes) {
size++;
minutes[0] = 55;
}
}size=1
minutes=[55, 0]size는 그대로 1이고 배열 원소는 55로 바뀝니다.
이를 “배열은 참조 전달”이라고 외우면 혼란스럽습니다.
모든 인수는 값으로 전달되며, 배열의 경우 복사되는 값이 같은 배열을 가리키는 참조입니다.
검증과 계산의 분리
public final class StudyLogMethods {
public static void main(String[] args) {
String[] topics = {"arrays", "", "methods"};
int[] minutes = {40, 30, 50};
for (int index = 0; index < topics.length; index++) {
if (isValid(topics[index], minutes[index])) {
printEntry(index, topics[index], minutes[index]);
} else {
System.out.println("rejected index=" + index);
}
}
}
private static boolean isValid(String topic, int minutes) {
return topic != null && !topic.isBlank() && minutes > 0;
}
private static void printEntry(int index, String topic, int minutes) {
System.out.println((index + 1) + ". " + topic + "=" + minutes);
}
}1. arrays=40
rejected index=1
3. methods=50isValid은 유효 여부만 판단하고 출력하지 않습니다.
printEntry는 이미 유효하다고 결정된 한 기록을 표현합니다.
검증 메서드가 콘솔 입력까지 읽거나 출력까지 하면 호출 순서와 재사용 조건이 복잡해집니다.
작은 메서드는 줄 수가 짧아서가 아니라 변경 이유가 하나라서 가치가 있습니다.
기록 기능의 메서드 분리
배열과 size를 함께 사용하는 프로그램에서 추가 메서드는 새 size를 반환하게 만들 수 있습니다.
기본형 size는 메서드 안에서 바꾸어도 호출자에게 반영되지 않기 때문입니다.
public final class ModularStudyLog {
public static void main(String[] args) {
String[] topics = new String[3];
int[] minutes = new int[3];
int size = 0;
size = add(topics, minutes, size, "arrays", 40);
size = add(topics, minutes, size, "methods", 50);
size = add(topics, minutes, size, "", 30);
printEntries(topics, minutes, size);
printSummary(minutes, size);
}
private static int add(
String[] topics,
int[] minutes,
int size,
String topic,
int value
) {
if (size >= topics.length || size >= minutes.length) {
System.out.println("full");
return size;
}
if (topic == null || topic.isBlank() || value <= 0) {
System.out.println("invalid");
return size;
}
topics[size] = topic;
minutes[size] = value;
System.out.println("added " + topic);
return size + 1;
}
private static void printEntries(String[] topics, int[] minutes, int size) {
for (int index = 0; index < size; index++) {
System.out.println((index + 1) + ". " + topics[index] + "=" + minutes[index]);
}
}
private static void printSummary(int[] minutes, int size) {
int total = total(minutes, size);
double average = size == 0 ? 0.0 : (double) total / size;
System.out.println("total=" + total + ", average=" + average);
}
private static int total(int[] minutes, int size) {
int result = 0;
for (int index = 0; index < size; index++) {
result += minutes[index];
}
return result;
}
}added arrays
added methods
invalid
1. arrays=40
2. methods=50
total=90, average=45.0세 번째 추가는 검증에 실패해 기존 size 2를 그대로 반환합니다.
호출자가 반환값을 다시 size에 대입해야 성공한 추가가 누적됩니다.
add는 공간 검사, 값 검사, 두 배열 쓰기, size 갱신 순서를 한곳에서 보장합니다.
배열 길이가 서로 다르면 병렬 관계가 이미 깨진 것입니다. 예제는 두 길이를 각각 검사하지만, 프로그램 시작 시 길이가 같다는 불변식을 확인하고 이후 한 쌍으로만 다루는 편이 좋습니다. 객체를 배우면 주제와 시간을 하나의 기록 객체로 묶어 이런 위험을 더 줄일 수 있습니다.
메서드 추출 기준
다음 신호가 보이면 메서드 추출을 검토합니다.
- 같은 문장 묶음이 둘 이상 반복됩니다.
- 코드 묶음에 “입력 검증”, “합계 계산”처럼 자연스러운 이름을 붙일 수 있습니다.
main의 한 화면에 입력, 저장, 집계, 출력이 뒤섞여 실행 흐름을 찾기 어렵습니다.- 규칙이 바뀔 때 여러 위치를 함께 수정해야 합니다.
반대로 한 줄마다 메서드로 쪼개면 호출을 따라다니느라 전체 흐름을 잃을 수 있습니다. 반복 제거와 의미 있는 책임 경계를 기준으로 나눕니다.
연습 문제
배열, size, 최소 시간을 받아 최소 시간 이상인 기록만 합산하는 qualifiedTotal 메서드를 작성하세요.
size가 배열 길이보다 크거나 minimum이 음수면 IllegalArgumentException을 던지게 합니다.
해설 보기
public final class QualifiedTotalMethod {
public static void main(String[] args) {
int[] minutes = {20, 45, 30, 60};
System.out.println(qualifiedTotal(minutes, 4, 30));
System.out.println(qualifiedTotal(minutes, 3, 40));
}
private static int qualifiedTotal(int[] minutes, int size, int minimum) {
if (size < 0 || size > minutes.length || minimum < 0) {
throw new IllegalArgumentException("invalid range");
}
int total = 0;
for (int index = 0; index < size; index++) {
if (minutes[index] >= minimum) {
total += minutes[index];
}
}
return total;
}
}135
45두 번째 호출은 앞의 세 기록만 보고 40분 이상인 45만 더합니다. 매개변수가 허용하는 범위를 메서드 입구에서 검사하면 본문의 반복은 정상 범위라는 전제 아래 단순하게 유지됩니다.