개요
좋은 소프트웨어 설계 핵심은 모듈화 수준이다.
모듈성이란 프로그램 어딘가를 수정 시 해당 기능과 관련된 극히 작은 부분만 이해 해도되는 능력이다.
모듈성이 높으려면 연관된 요소를 하나의 요소로 묶고, 요소 사이 연결 관계를 쉽게 이해할 수 있어야 한다.
객체 지향 프로그래밍의 핵심 모듈화 컨텍스트는 클래스다.
A 함수가 자신이 속한 모듈보다 다른 모듈을 더 참조한다면 옮겨야한다.
예시: 중첩 함수를 최상위로 옮기기
/* GPS 추적 기록의 총 거리를 계산하는 함수 */ function trackSummary(points){ const totalTime = calculateTime(); const totalDistance = calculateDistance(); const pace = totalTime / 60 / totalDistance; return { time : totalTime, distance : totalDistance, pace : pace, }; function calculateDistance() { //총 거리 계산 let result = 0; for(let i = 1; i < points.length ; i++){ result += distance(points[i-1], points[i]); } return result; } function distance(p1, p2) {/* 두 지점의 거리 계산 */} function radians(degrees) {/* 라디안값으로 변환 */} function calculateTime() {/* 총 시간 계산 */} } |
중첩 함수 calculateDistance()를 최상위로 옮겨서 다른 정보와 독립적으로 계산하고 싶다. |
function trackSummary(points){ const totalTime = calculateTime(); const totalDistance = calculateDistance(); const pace = totalTime / 60 / totalDistance; return { time : totalTime, distance : totalDistance, pace : pace, }; function calculateDistance() { //총 거리 계산 let result = 0; for(let i = 1; i < points.length ; i++){ result += distance(points[i-1], points[i]); } return result; } function distance(p1, p2) {/* 두 지점의 거리 계산 */} function radians(degrees) {/* 라디안값으로 변환 */} function calculateTime() {/* 총 시간 계산 */} } function top_calculateDistance() { //최상위로 복사,임시이름 let result = 0; for(let i = 1; i < points.length ; i++){ result += distance(points[i-1], points[i]); } return result; } |
function trackSummary(points){ const totalTime = calculateTime(); const totalDistance = calculateDistance(); const pace = totalTime / 60 / totalDistance; return { time : totalTime, distance : totalDistance, pace : pace, }; function calculateDistance() { //총 거리 계산 let result = 0; for(let i = 1; i < points.length ; i++){ result += distance(points[i-1], points[i]); } return result; //distance, radians 는 현재 문맥에서 calculateDistance에서만 사용중 //따라서 calculateDistance()함수 안으로 옮기는게 적절하다. function distance(p1, p2) { const EARTH_RADIUS = 3959; // 단위 마일 const dLat = radians(p2.lat) - radians(p1.lat); const dLon = radians(p2.lon) - radians(p1.lon); const a = Math.pow(Math.sin(dLat / 2), 2) + Math.cos(radians(p2.lat)) * Math.cos(radians(p1.lat)) * Math.pow(Math.sin(dLon /2), 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); return EARTH_RADIUS * c; } function radians(degrees) { return degrees * Math.PI / 180; } } function calculateTime() {/* 총 시간 계산 */} } function top_calculateDistance(points) { let result = 0; for(let i = 1; i < points.length ; i++){ result += distance(points[i-1], points[i]); } return result; } |
function top_calculateDistance(points) { let result = 0; for(let i = 1; i < points.length ; i++){ result += distance(points[i-1], points[i]); } return result; //top_calculateDistance()함수도 똑같이 옮겨준다. function distance(p1, p2) { const EARTH_RADIUS = 3959; // 단위 마일 const dLat = radians(p2.lat) - radians(p1.lat); const dLon = radians(p2.lon) - radians(p1.lon); const a = Math.pow(Math.sin(dLat / 2), 2) + Math.cos(radians(p2.lat)) * Math.cos(radians(p1.lat)) * Math.pow(Math.sin(dLon /2), 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); return EARTH_RADIUS * c; } function radians(degrees) { return degrees * Math.PI / 180; } } |
function trackSummary(points){ const totalTime = calculateTime(); const totalDistance = calculateDistance(); const pace = totalTime / 60 / totalDistance; return { time : totalTime, distance : totalDistance, pace : pace, }; //복사 함수를 호출하도록 수정한다. function calculateDistance() { //총 거리 계산 return top_calculateDistance(points); } function calculateTime() {/* 총 시간 계산 */} } |
이 시점에서 반드시 테스트를 해 정상 동작을 확인한다. 정상 동작한다면 기존 함수 제거 및 복사 함수를 직접 호출하도록 수정한다. |
function trackSummary(points){ const totalTime = calculateTime(); const totalDistance = top_calculateDistance(); const pace = totalTime / 60 / totalDistance; return { time : totalTime, distance : totalDistance, pace : pace, }; function calculateTime() {/* 총 시간 계산 */} } |
function trackSummary(points){ const totalTime = calculateTime(); const pace = totalTime / 60 / totalDistance(); return {//적절한 이름으로 변경 후 인라인 time : totalTime, distance : totalDistance(), pace : pace, }; function calculateTime() {/* 총 시간 계산 */} } |
function trackSummary(points){ const totalTime = calculateTime(); const pace = totalTime / 60 / totalDistance(); return { time : totalTime, distance : totalDistance(), pace : pace, }; function calculateTime() {/* 총 시간 계산 */} } function totalDistance(points) { let result = 0; for(let i = 1; i < points.length ; i++){ result += distance(points[i-1], points[i]); } return result; } //distance, radians도 totalDistance안에 어떤 것도 의존하지 않으니 //최상단으로 옮겼다. function distance(p1, p2) { const EARTH_RADIUS = 3959; // 단위 마일 const dLat = radians(p2.lat) - radians(p1.lat); const dLon = radians(p2.lon) - radians(p1.lon); const a = Math.pow(Math.sin(dLat / 2), 2) + Math.cos(radians(p2.lat)) * Math.cos(radians(p1.lat)) * Math.pow(Math.sin(dLon /2), 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); return EARTH_RADIUS * c; } function radians(degrees) { return degrees * Math.PI / 180; } |
예시: 다른 클래스로 옮기기
class Account{ get bankCharge(){ //은행이자 let result = 4.5; if(this._daysOverdrawn > 0) result += this.overdraftCharge; return result } get overdraftCharge(){ if(this.type.isPremium){//초과 인출 이자 계산 const baseCharge = 10; if(this.daysOverdrawn <= 7){ return baseCharge; }else{ return baseCharge + (this.daysOverdrawn - 7) * 0.85; } }else{ return this.daysOverdrawn * 1.75; } } } |
계좌 종류에 따라 이자 책정 알고리즘이 달라지도록 수정 |
class AccountType{ //계좌 타입으로 복사 후 적절하게 수정 //daysOverdrawn는 계좌별로 달라지므로 원래 클래스에 남겨두었다. overdraftCharge(daysOverdrawn){ if(this.isPremium){ const baseCharge = 10; if(this.daysOverdrawn <= 7){ return baseCharge; }else{ return baseCharge + (daysOverdrawn - 7) * 0.85; } }else{ return daysOverdrawn * 1.75; } } } |
class Account{ get bankCharge(){ let result = 4.5; if(this._daysOverdrawn > 0) result += this.overdraftCharge; return result } get overdraftCharge(){//위임 메서드 return this.type.overdraftCharge(this.daysOverdrawn); } } class AccountType{ overdraftCharge(daysOverdrawn){ if(this.isPremium){ const baseCharge = 10; if(this.daysOverdrawn <= 7){ return baseCharge; }else{ return baseCharge + (daysOverdrawn - 7) * 0.85; } }else{ return daysOverdrawn * 1.75; } } } |
class Account{ get bankCharge(){ let result = 4.5; if(this._daysOverdrawn > 0) //인라인할지 고민한 결과 인라인하기로 결정 result += this.type.overdraftCharge(this.daysOverdrawn); return result } } class AccountType{ overdraftCharge(daysOverdrawn){ if(this.isPremium){ const baseCharge = 10; if(this.daysOverdrawn <= 7){ return baseCharge; }else{ return baseCharge + (daysOverdrawn - 7) * 0.85; } }else{ return daysOverdrawn * 1.75; } } } |
'IT책, 강의 > 리팩터링' 카테고리의 다른 글
08 - 기능이동 - 문장을 함수로 옮기기 (0) | 2023.08.05 |
---|---|
08 - 기능이동 - 필드 옮기기 (0) | 2023.08.04 |
07 - 캡슐화 - 알고리즘 교체하기 (0) | 2023.08.01 |
07 - 캡슐화 - 중개자 제거하기 (0) | 2023.07.31 |
07 - 캡슐화 - 위임 숨기기 (0) | 2023.07.30 |