11. 한번에 하나씩
- '한 번에 한 가지' 일만 하도록 논리적 영역 단위로 코드를 재조직하라
- 모든 작업을 나열해보아라
- 그 작업들을 서로 다른 함수로 쪼개어도 좋고, 최소한 한 함수 안에서라도 구분해서 놓아두라.
작업은 작을 수 있다.
// 투표는 '추천', '반대', '' 세 가지 경우의 수를 가진다.
// 이 함수는 기존의 투표를 수정하는데 사용한다.
var voteChanged = function(oldVote, newVote) {
var score = getScore();
if (newVote !== oldVote) {
if (newVote === 'Up') {
score += (oldVote === 'Down' ? 2 : 1);
} else if (newVote === 'Down') {
score -= (oldVote === 'Up' ? 2 : 1);
} else if (newVote === '') {
score += (oldVote === 'Up' ? 1 : -1);
}
}
setScore(score);
}
작업 목록
- old_vote와 new_vote가 각각 수치값으로 해석된다.
- 수치값을 반영하여 총 점수가 변경된다.
// vote 값에서 수치값 획득
var getVoteValue = function(vote) {
switch(vote) {
case 'Up':
return +1;
case 'Down':
return -1;
default:
return 0;
}
}
// 수치값을 기준으로 수정
var voteChanged(oldVote, newVote) {
var score = getScore();
score -= getVoteValue(oldVote) // 이전 값 제거
score += getVoteValue(newVote);
setScore(score);
}
객체에서 값 추출하기
var location_info = {
LocalityName: "",
SubAdministrativeAreaName: "",
AdministrativeAreaName: "",
CountryName: ""
}
- 목표 값은 LocalityName + ", " + CountryName
- LocalityName이 없으면 SubAdministrativeAreaName, SubAdministrativeAreaName도 없으면 AdministrativeAreaName로 대체한다고 가정
var place = location_info["LocalityName"];
if (!place) {
place = location_info["SubAdministrativeAreaName"];
}
if (!place) {
place = location_info["AdministrativeAreaName"];
}
if (!place) {
place = "Middle-of-Nowhere";
}
if (location_info["CountryName"]) {
place += ", " + location_info["CountryName"];
} else {
place += ", Planet Earth";
}
그런데 만약에 미국일 때만 CountryName 대신에 AdministrativeAreaName를 써야한다면?
작업 목록
- location_info 객체에서 값을 읽어 저장하기.
- '도시'의 값을 정하기 위해서 정해진 순서대로 탐색. 모두 비어있다면 '아무 곳도 아닌 곳'이라는 기본값을 입력
- '나라' 값을 정함. 비어있다면 '지구'로 설정
- place 변경
// 필요한 값들의 저장
var town = locationInfo.LocalityName;
var city = locationInfo.SubAdministrativeAreaName;
var state = locationInfo.AdministrativeAreaName;
var country = locationInfo.CountryName;
// '도시' 값 설정
// 기본값에서 시작하여 더 자세한 값으로 파고들어라
var firstHarf = "Middle-of-Nowhere";
if (state && country !== "USA") {
firstHarf = state;
}
if (city) {
firstHarf = city;
}
if (town) {
firstHarf = town;
}
// '나라' 값 설정
var secondHarf = "Planet Earth";
if (country) {
secondHarf = country;
}
if (state && country === "USA") {
secondHarf = state;
}
return firstHarf + ", " + secondHarf;
더 큰 예제
웹 크롤링 시스템에서 각 웹페이지를 호출해서 모두 받고 나면 호출되는 함수
function updateCounts(httpDownload) { counts["ExitState"][httpDownload.exitState()] += 1; counts["HttpResponse"][httpDownload.httpResponse()] += 1; counts["Content-Type"][httpDownload.contentType()] += 1; }
그러나 httpDownload가 위와 같은 메소드들을 제공하지 않음
- 또한 값이 아예 존재하지 않을 수도 있음.
function updateCounts(httpDownload) {
// 값이 있으면 exitState 값을 찾는다
if ((httpDownload.hasEventLog() == false) || (httpDownload.hasExitState() == false)) {
counts["ExitState"]["unknown"] += 1;
} else {
var stateStr = ExitStateTypeName(httpDownload.eventLog().exitState());
counts["ExitState"][stateStr] += 1;
}
// 만약에 HTTP 헤더가 아예 없으면 unknown 추가
if (httpDownload.hasHttpHeaders() == false) {
counts["HttpResponse"]["unknown"] += 1;
counts["Content-Type"]["unknown"] += 1;
return;
}
// 값이 있으면 httpResponse를 기록
var headers = httpDownload.httpHeaders();
if (headers.hasResponseCode() == false) {
counts["HttpResponse"]["unknown"] += 1;
} else {
var code = StringPrintf("%d", headers.responseCode());
counts["HttpResponse"][code] += 1;
}
// 값이 있으면 Content-Type 값을 기록
if (headers.hasContentType() == false) {
counts["Content-Type"]["unknown"] += 1;
} else {
var contentType = ContentTypeMime(headers.contentType());
counts["Content-Type"][contentType] += 1;
}
}
작업 목록
- 기본값 "unknown" 할당
- httpDownload의 멤버 값 확인
- 값을 읽어서 문자열로 변환
- counts[] 갱신
function updateCounts(httpDownload) {
// 1. 기본값 "unknown" 할당
var exitState = "unknown",
httpResponse = "unknown",
contentType = "unknown";
// 2 & 3. httpDownload에서 값을 확인하고 문자열로 변환
if (httpDownload.hasEventLog() && httpDownload.eventLog().hasExitState()) {
exitState = ExitStateTypeName(httpDownload.eventLog().exitState());
}
if (httpDownload.hasHttpHeaders() && httpDownload.httpHeaders().hasResponseCode()) {
httpResponse = StringPrintf("%d", httpDownload.httpHeaders().responseCode());
}
if (httpDownload.hasHttpHeaders() && httpDownload.httpHeaders().hasContentType()) {
contentType = ContentTypeMime(httpDownload.httpHeaders().contentType());
}
// 4. counts[] 갱신
counts["ExitState"][exitState] += 1;
counts["HttpResponse"][httpResponse] += 1;
counts["Content-Type"][contentType] += 1;
}
- 위의 개선된 코드에서 함수를 추출해내는 것도 좋은 방법.
function getExitState(httpDownload) {
if (httpDownload.hasEventLog() && httpDownload.eventLog().hasExitState()) {
return ExitStateTypeName(httpDownload.eventLog().exitState());
} else {
return "unknown";
}
}