F-LAB JAVA · 5주차 · Phase 4 · 관심사의 분리
★ 깊이 파기 — 리팩토링 1단계
이 Unit을 끝내면 다음을 답할 수 있어야 한다.
메서드 추출 (Extract Method) 은 여러 메서드에 중복된 코드를 하나의 메서드로 모으는 리팩토링으로, 반복되는 DB 연결 코드를 getConnection() 으로 추출하면 중복이 제거되고 DB 정보 변경 시 한 곳만 수정하면 된다.
추출 전에는 add·get·update 등 모든 메서드가 각자 드라이버 로딩과 연결 생성 코드를 반복했지만, 추출 후에는getConnection()한 메서드가 그 책임을 맡고 나머지 메서드는 이를 호출만 한다.
이로써 중복이 제거 되고, DB 접속 정보가 바뀌면 getConnection() 한 곳만 수정 하면 되어 "변경 1번 = 수정 N곳" 문제가 "변경 1번 = 수정 1곳" 으로 줄어든다.
이 단계는 SOLID 의 DRY 와 SRP 를 부분적으로 달성하지만, 아직 한계가 있다 — getConnection() 이 여전히 같은 클래스 안에 있어 고객사마다 다른 DB 연결 방식 을 지원하려면 코드를 수정해야 한다.
따라서 다음 단계에서는 getConnection() 을 추상 메서드 로 만들어 서브클래스가 연결 방식을 결정하게 한다 (Unit 4.3).
메서드 추출 = 공통 준비 한 곳에:
추출 전 (요리사마다 준비):
- 김치찌개 요리사: 불 켜고, 냄비 꺼내고...
- 된장찌개 요리사: 불 켜고, 냄비 꺼내고... (중복)
- 부대찌개 요리사: 불 켜고, 냄비 꺼내고... (중복)
추출 후 (준비 담당 한 명):
- 준비 담당: 불 켜고 냄비 준비 (getConnection)
- 각 요리사: "준비된 것 주세요" (호출)
효과:
- 준비 방식 바뀌면 (가스 → 인덕션)
- 준비 담당만 바꿈 (한 곳)
- 요리사들은 그대로
한계:
- 준비 담당이 여전히 같은 주방
- 다른 주방 방식 (고객사 DB) 못 함
→ 준비를 외부에 맡겨야 (다음 단계)
→ 메서드 추출 = 중복을 한 메서드로, DB 변경 = 한 곳, 하지만 다른 DB 한계.
1. 메서드 추출이란
2. Before — 중복
3. After — 추출
4. 중복 제거의 이점
5. 변경 범위 변화
6. 적용된 SOLID
7. 메서드 추출의 한계
8. 남은 문제와 다음 단계
9. 면접 + 자기 점검
메서드 추출 (Extract Method):
중복되거나 의미 있는 코드를
별도 메서드로 분리.
- 중복 제거
- 의미 명확화
- 재사용
대표 리팩토링 기법:
- Extract Method (메서드 추출)
- Extract Class (클래스 추출)
- Rename (이름 변경)
- Move (이동)
메서드 추출 = 가장 기본
추출 대상:
- 중복된 코드
- 긴 메서드의 일부
- 의미 있는 묶음
DAO: 연결 생성 코드
메서드 추출 효과:
- 중복 제거 (DRY)
- 한 곳 관리
- 의미 부여 (이름)
- 재사용
// 추출 대상: 반복되는 연결 코드
public class ShipmentDao {
public void add(Shipment s) throws Exception {
// 이 부분이 중복 (추출 대상)
Class.forName("com.mysql.jdbc.Driver");
Connection c = DriverManager.getConnection(
"jdbc:mysql://localhost/ilic", "root", "password");
// SQL...
}
// get, update 등에도 같은 코드 (중복)
// → getConnection() 으로 추출
}
메서드 추출 (Extract Method) 이란?
답:
1. 추출:
리팩토링:
대상:
효과:
// Before — 각 메서드가 직접 연결
public class ShipmentDao {
public void add(Shipment shipment) throws Exception {
Class.forName("com.mysql.jdbc.Driver"); // 중복
Connection c = DriverManager.getConnection( // 중복
"jdbc:mysql://localhost/ilic", "root", "password");
PreparedStatement ps = c.prepareStatement(
"insert into shipments(id, bl_no, weight) values(?, ?, ?)");
ps.setLong(1, shipment.getId());
ps.executeUpdate();
ps.close();
c.close();
}
public Shipment get(Long id) throws Exception {
Class.forName("com.mysql.jdbc.Driver"); // 중복
Connection c = DriverManager.getConnection( // 중복
"jdbc:mysql://localhost/ilic", "root", "password");
PreparedStatement ps = c.prepareStatement(
"select * from shipments where id = ?");
ps.setLong(1, id);
ResultSet rs = ps.executeQuery();
Shipment shipment = null;
if (rs.next()) {
shipment = mapRow(rs);
}
rs.close();
ps.close();
c.close();
return shipment;
}
private Shipment mapRow(ResultSet rs) throws SQLException {
Shipment s = new Shipment();
s.setId(rs.getLong("id"));
return s;
}
}
중복 식별:
add 와 get 의 공통:
Class.forName("com.mysql.jdbc.Driver");
Connection c = DriverManager.getConnection(
"jdbc:mysql://localhost/ilic", "root", "password");
→ 완전히 동일 (추출 대상)
중복의 문제:
- DB 정보 변경 → 모든 메서드
- 같은 코드 반복
- 가독성 ↓
- 유지보수 ↓
Before (중복) 의 모습은?
답:
1. 중복:
공통:
문제:
추출 대상:
// After — getConnection 추출
public class ShipmentDao {
// 추출된 연결 메서드 (한 곳)
private Connection getConnection() throws Exception {
Class.forName("com.mysql.jdbc.Driver");
return DriverManager.getConnection(
"jdbc:mysql://localhost/ilic", "root", "password");
}
public void add(Shipment shipment) throws Exception {
Connection c = getConnection(); // 추출 메서드 호출 (깔끔)
PreparedStatement ps = c.prepareStatement(
"insert into shipments(id, bl_no, weight) values(?, ?, ?)");
ps.setLong(1, shipment.getId());
ps.executeUpdate();
ps.close();
c.close();
}
public Shipment get(Long id) throws Exception {
Connection c = getConnection(); // 추출 메서드 호출 (깔끔)
PreparedStatement ps = c.prepareStatement(
"select * from shipments where id = ?");
ps.setLong(1, id);
ResultSet rs = ps.executeQuery();
Shipment shipment = null;
if (rs.next()) {
shipment = mapRow(rs);
}
rs.close();
ps.close();
c.close();
return shipment;
}
private Shipment mapRow(ResultSet rs) throws SQLException {
Shipment s = new Shipment();
s.setId(rs.getLong("id"));
return s;
}
}
변화:
전:
add, get 마다 연결 코드
후:
getConnection (한 곳)
add, get → getConnection() 호출
→ 중복 제거
깔끔해진 메서드:
add 메서드:
- 연결 코드 사라짐
- SQL 에 집중
- 읽기 쉬움
→ 관심사 (SQL) 명확
After (추출) 의 변화는?
답:
1. 추출:
변화:
깔끔:
중복:
중복 제거 이점:
1. 한 곳 관리
2. 변경 한 곳
3. 가독성
4. 재사용
5. 정합성
한 곳 관리:
연결 코드:
- getConnection 한 곳
- 변경 시 여기만
- 명확
가독성:
전 (add):
드라이버 + 연결 + SQL + 해제
(복잡, 핵심 안 보임)
후 (add):
getConnection() + SQL + 해제
(SQL 핵심 명확)
재사용:
getConnection:
- add, get, update, delete 모두 사용
- 한 메서드 공유
- DRY
public class ShipmentDao {
// 한 곳 — 모든 메서드가 재사용
private Connection getConnection() throws Exception {
Class.forName("com.mysql.jdbc.Driver");
return DriverManager.getConnection(
"jdbc:mysql://localhost/ilic", "root", "password");
}
// 재사용 1
public void add(Shipment s) throws Exception {
Connection c = getConnection();
// ...
}
// 재사용 2
public Shipment get(Long id) throws Exception {
Connection c = getConnection();
return null;
}
// 재사용 3
public void update(Shipment s) throws Exception {
Connection c = getConnection();
// ...
}
// 재사용 4
public List<Shipment> findByStatus(String status) throws Exception {
Connection c = getConnection();
return List.of();
}
// getConnection 한 곳, 4곳 재사용
}
중복 제거의 이점은?
답:
1. 한 곳 관리:
변경 한 곳:
가독성:
재사용:
변경 범위 변화:
전: 변경 1번 = 수정 N곳
후: 변경 1번 = 수정 1곳
DB 정보 변경:
- getConnection 만
// DB 비밀번호 변경
// 전: 모든 메서드 수정
// 후: getConnection 한 곳만
private Connection getConnection() throws Exception {
Class.forName("com.mysql.jdbc.Driver");
return DriverManager.getConnection(
"jdbc:mysql://localhost/ilic", "root",
"new_password"); // 여기만 수정
}
// add, get, update ... 안 건드림
효과:
- 수정 비용 ↓ (1곳)
- 실수 ↓ (한 곳)
- 누락 X (한 곳)
- 정합성 보장
여전한 한계:
DB 정보 변경: 1곳 (개선)
하지만:
- DB 종류 변경
- 고객사별 다른 연결
→ getConnection 수정 (한계)
public class ShipmentDao {
// DB 접속 정보 변경 → 여기만
private Connection getConnection() throws Exception {
// 비밀번호 변경 → 이 줄만
Class.forName("com.mysql.jdbc.Driver");
return DriverManager.getConnection(
"jdbc:mysql://localhost/ilic", // 서버 변경 → 이 줄만
"ilic_user", // 계정 변경 → 이 줄만
"secure_password"); // 비밀번호 변경 → 이 줄만
}
// 모든 데이터 접근 메서드는 영향 X
// → 변경 1곳 (개선)
// 단, DB 종류 (MySQL → Oracle) 는?
// → getConnection 수정 (아직 한계)
// → 다음 Unit (추상화)
}
DB 정보 변경 시 수정 범위 변화는?
답:
1. 축소:
DB 변경:
효과:
한계:
DRY 적용:
중복 제거:
- 연결 코드 한 곳
- 반복 X
→ DRY 달성
SRP 부분 달성:
getConnection:
- 연결 생성 책임만
add, get:
- SQL 책임 (연결은 위임)
→ 책임 약간 분리
아직 부족:
SRP 완전 X:
- getConnection 이 같은 클래스
- 연결 + SQL 한 클래스
OCP X:
- DB 종류 변경 시 수정
DIP X:
- 여전히 구체 의존
점진적 개선:
Unit 4.2 (지금):
- DRY, SRP 부분
Unit 4.3:
- 추상화 (OCP 향)
Phase 6:
- 인터페이스 (OCP, DIP)
public class ShipmentDao {
// DRY 달성 (중복 제거)
// SRP 부분 (연결 책임 메서드 분리)
private Connection getConnection() throws Exception {
Class.forName("com.mysql.jdbc.Driver");
return DriverManager.getConnection(
"jdbc:mysql://localhost/ilic", "root", "password");
}
public void add(Shipment s) throws Exception {
Connection c = getConnection(); // 연결 위임
// SQL 책임만
}
// 아직:
// - getConnection 이 같은 클래스 (SRP 부분만)
// - DB 종류 변경 시 수정 (OCP X)
// - 구체 의존 (DIP X)
// → 다음 단계 개선
}
적용된 SOLID 원칙은?
답:
1. DRY:
SRP (부분):
아직 부족:
점진적:
메서드 추출 한계:
같은 클래스 안:
- getConnection 이 ShipmentDao 내부
- 연결 방식 고정
- 변경 시 수정
DB 종류 변경 한계:
MySQL → Oracle:
- getConnection 수정 필요
- 기존 코드 변경 (OCP X)
고객사별 DB:
"고객사마다 다른 DB"
- getConnection 하나로 불가
- 고객사별 다른 연결
- 코드 수정 (한계)
코드 공개 문제:
"UserDao 코드는 공개하고 싶지 않다.
그런데 고객사가 자기 DB 연결을 쓰게 하고 싶다"
- getConnection 이 내부 → 고객사가 못 바꿈
- 소스 수정 없이 연결 교체 불가
→ 추상화 필요
public class ShipmentDao {
private Connection getConnection() throws Exception {
// MySQL 고정 (한계)
Class.forName("com.mysql.jdbc.Driver");
return DriverManager.getConnection(
"jdbc:mysql://localhost/ilic", "root", "password");
}
// 한계 시나리오:
// 1. 고객사 A: Oracle 사용 → getConnection 수정
// 2. 고객사 B: PostgreSQL → 또 수정
// 3. ShipmentDao 코드를 고객사에 공개 X
// → 고객사가 연결 못 바꿈
// → getConnection 을 추상화해야
// → 서브클래스/외부가 연결 결정 (다음 Unit)
}
메서드 추출의 한계는?
답:
1. 한계:
DB 종류:
고객사별:
코드 공개:
남은 문제:
1. DB 종류 변경 → 수정 (OCP X)
2. 고객사별 DB → 불가
3. 코드 공개 없이 교체 X
4. 구체 의존 (DIP X)
다음 단계 — 추상화:
getConnection 을 추상 메서드로:
- 흐름은 부모 (변하지 않음)
- 연결은 자식 (변함)
→ 서브클래스가 연결 결정
→ Unit 4.3
추상화 효과:
- DB 종류 = 새 서브클래스 (확장)
- 부모 코드 변경 X (OCP 향)
- 고객사가 서브클래스 작성
→ 한계 극복
진화 로드맵:
Unit 4.2 (지금): 메서드 추출
- 중복 제거
Unit 4.3: 추상클래스
- 서브클래스 연결
Phase 5: 디자인 패턴
- 템플릿/팩토리
Phase 6: 인터페이스
- OCP, 전략
Phase 7~8: IoC/DI
- 외부 주입
// 다음 단계 미리보기 (추상화)
// 현재 (Unit 4.2)
public class ShipmentDao {
private Connection getConnection() throws Exception {
// MySQL 고정
return null;
}
}
// 다음 (Unit 4.3) — 추상 메서드
public abstract class ShipmentDao {
public void add(Shipment s) throws Exception {
Connection c = getConnection(); // 변하지 않는 흐름
// SQL...
}
public abstract Connection getConnection() throws Exception; // 변하는 부분
}
// 고객사별 서브클래스
class MySqlShipmentDao extends ShipmentDao {
public Connection getConnection() { return null; /* MySQL */ }
}
class OracleShipmentDao extends ShipmentDao {
public Connection getConnection() { return null; /* Oracle */ }
}
// DB 종류 = 새 서브클래스 (부모 변경 X)
아직 남은 문제와 다음 단계는?
답:
1. 남은 문제:
다음 단계:
효과:
로드맵:
| Q | 핵심 답변 |
|---|---|
| 메서드 추출? | 코드를 별도 메서드 |
| Before → After? | 중복 → 한 곳 |
| getConnection 효과? | 중복 제거 |
| 중복 제거 이점? | 변경 한 곳, 재사용 |
| 변경 범위? | N곳 → 1곳 |
| 적용 SOLID? | DRY, SRP (부분) |
| 한계? | DB 종류 변경 수정 |
| 남은 문제? | 고객사별 DB, OCP |
| 다음 단계? | 추상 메서드 |
| 진화? | 추상화 → DI |
답:
답:
답:
답:
답:
1. 메서드 추출
2. 변경 범위
3. 한계
이번 Unit에서 메서드 추출을 봤다면, 다음은 추상클래스 확장 (Phase 4 마지막).
🌱 Phase 4 — 관심사의 분리 (★ 깊이)
✅ Unit 4.1 관심사 분리 개념
✅ Unit 4.2 1단계: 메서드 추출 ← 여기
⏭ Unit 4.3 2단계: 추상클래스로 확장
✅ Part A — 동시성 마무리 (7 Unit)
🌱 Part B — 토비의 스프링
✅ Phase 3 — 전통 DAO (3 Unit)
🌱 Phase 4 — 관심사의 분리 (2/3, ★ 깊이)
총: 12/26 Unit