프로그래밍 초창기의 시스템 = 루틴 + 하위 루틴
포트란 & PL/1 시기의 시스템 = 프로그램 + 하위 프로그램 + 함수
→ 현재 : 함수만 살아남음
가장 기본적인 단위 : 함수
함수 코드 보기(좋지 않은 예)
// ❌
function testableHtml(pageData, includeSuiteSetup){
try{
const wikiPage = pageData.getWikiPage();
const buffer = StringBuffer();
if (pageData.hasAttribute("Test")){
if (includeSuiteSetup){
const suiteSetup = PageCrawlerImpl.getInheritedPage(SuiteResponder.SUITE_SETUP_NAME, wikiPage);
if (suiteSetup != null){
pagePath = suiteSetup.getPageCrawler().getFullPath(suiteSetup);
pagePathName = PathParser.render(pagePath);
buffer.append("!include -setup .");
buffer.append(pagePathName);
buffer.append("\n");
}
}
const setup = PageCralwerImpl.getInheritedPage("Setup", wikiPage);
if (setup != null){
const setupPath = wikiPage.getPageCrawler().getFullPath(setup);
const setupPathName = PathParser.render(setupPath);
buffer.append("!include -setup .");
buffer.append(setupPathName);
buffer.append("\n");
}
}
buffer.append(pageData.getContent())
if (pageData.hasAttribute("Test")){
const teardown = PageCrawlerImpl.getInheritedPage("TearDown", wikiPage);
if (teardown != NULL){
const tearDownPath = wikiPage.getPageCralwer().getFullPath(teardown);
const tearDownPathName = PathParser.render(tearDownPath);
buffer.append("\n");
buffer.append("!include -teardown .");
buffer.append(tearDownPathName);
buffer.append("\n");
}
if (includeSuiteSetup)
const suiteTeardown = PageCrawlerImpl.getInheritedPage(SuiteResponder.SUITE_TEARDOWN_NAME, wikiPage);
if (suiteTeardown != NULL){
const pagePath = suiteTeardown.getPageCrawler().getFullPath(suiteTeardown);
const pagePathName = PathParser.render(pagePath);
buffer.append("!include -teardown .");
buffer.append(pagePathName);
buffer.append("\n");
}
}
}
pageData.setContent(String(buffer));
return pageData.getHtml();
}
위 함수가 좋지 않은 이유
1차로 개선된 코드
// 위 코드 리팩터링 버전
function renderPageWithSetupsAndTeardowns(pageData, isSuite){
try{
const isTestPage = pageData.hasAttribute("Test");
if (isTestPage){
const testPage = pageData.getWikiPage();
const newPageContent = StringBuffer();
includeSetupPages(testPage, newPageContent, isSuite);
newPageContent.append(pageData.getContent());
includeTeardownpages(testPage, newPageContent, isSuite);
pageData.setContent(str(newPageContent));
}
}
return pageData.getHTML();
}
// 리-리팩토링한 코드
function renderPageWithSetupsAndTeardowns(pageData, isSuite) {
try{
if (isTestPage(pageData))
includeSetupAndTeardownPages(pageData, isSuite);
}
return pageData.getHtml();
}
함수는 한 가지를 해야 한다. 그 한 가지를 잘 해야한다. 그 한가지만을 해야 한다.
function calculatePay(e){
const etype = e.type
switch (etype){
case "COMMISSIONED":
return calculateCommissionedPay(e);
case "HOURLY":
return calculateHourlyPay(e);
case "SALARIED":
return calculateSalariedPay(e);
default:
console.log(InvalidEmployeeType(e.type));
}
}
class Employee(){
isPayday(){
//pass
}
calculatePay(){
//pass
}
deliveryPay(pay){
//pass
}
}
class EmployeeFactory(){
makeEmployee(r){
//pass
}
}
class EmployeeFactoryImpl(EmployeeFactory){
makeEmployee(r){
switch (r.type){
case "COMMISIONED":
return CommisionedEmployee(r);
case "HOURLY":
return HourlyEmployee(r);
case "SALARIED":
return SalariedEmployee(r);
default:
console.log(InvalidEmployeeType(r.type));
}
}
}
includeSetupAndTeardownPages
, includeSetupPages
, ...함수에서 이상적인 이수 개수 : 0개(무항) > 1개(단항) > 2개(이항) > ...
→ 3개 이상 : 피하는 것이 좋음
boolean fileExists("MyFile")
InputStream fileopen("MyFile")
가급적 플래그 인수는 사용하지 말자.
bool
값을 넘기는 관례 → 함수가 한꺼번에 여러 가지를 처리한다고 공표하는 것assertEquals(1.0, amount, .001)
: 그다지 음험하지 않은 삼항 함수인수가 2~3개 필요할 경우 : 일부 독자적인 클래스 변수로 선언할 가능성을 짚어볼 것
function makeCircle(x, y, radius){
// pass
}
function makeCircle(center, radius){
// pass
}
종종 인수 개수가 가변적인 함수도 필요
함수의 의도 & 인수 순서 & 의도의 정확한 표현 → 좋은 함수 이름 필수
write(name)
→ writeField(name)
assertEquals
→ assertExceptedEqualsActual(expected, actual)
class UserValidator{
constructor(cryptographer) {
this.cryptographer = "";
}
checkPassword (userName, password){
const user = UserGateway.findByName(userName);
if (user != User.NULL){
const codedPhrase = user.getPhraseEncodedByPassword();
const phrase = cryptographer.decrypt(codedPhrase, password);
if ("Valid Password" == phrase){
Session.initialize();
return True;
}
}
return False;
}
}
Session.initialize()
의 호출checkPassword
(암호 확인)appendFooter(report)
를 봐야 호출부 appendFooter(s)
의 s
가 출력 인수임을 알 수 있음함수 : 뭔가를 수행 or 뭔가에 답함 둘 중 하나만 해야 함
function set(attribute, value){
// pass
}
// ------------------------------------------
if set("username", "unclebob") ...
attribute
인 속성을 찾아 값을 value
로 설정 후 성공 시 true
, 실패 시 false
반환set
이라는 단어 : 동사인지 형용사인지 분간하기 어려움 → 함수 호출 코드만 봤을 경우 의미 모호set
- 동사 의미 vs if문 안에 넣을 경우 - 형용사로 느껴짐if (element.hasAttributes("username"))
element.setAttribute("username", "unclebob");
명령 함수에서 오류 코드를 반환하는 방식 : 명령/조회 분리 규칙 위반
if (deletePage(page) == E_OK)
// pass
if (deletePage(page) == E_OK){
if (registry.deleteReference(page.name) == E_OK){
if (configKeys.deleteKey(page.name.makeKey()) == E_OK)
consolel.log("page deleted")
else
console.log("configKey not deleted");
else
console.log("deleteReference from registry failed");
}
else:
console.log("delete failed");
}
throw new Error();
try{
deletePage(page);
registry.deleteReference(page.name);
configKeys.deleteKey(page.name.makeKey());
}
catch (e){
logError(e);
}
function delete(page){
try{
deletePageAndAllReferences(page);
}
catch (e){
logError(e);
}
}
function deletePageAndAllReferences(page){
deletePage(page);
registry.deleteReference(page.name);
configKeys.deleteKey(page.name.makeKey());
}
function logError(e){
connsole.error(e);
}
오류를 처리하는 함수 : 오류만 처리
try
문으로 시작해 catch/finally
문으로 끝나야 함오류 코드의 반환 : 클래스 or 열거형 변수 or ... 어디선가 오류 코드를 정의한다는 의미
public enum Error{
OK,
INVALID,
NO_SUCH,
LOCKED,
OUT_OF_RESOURCES,
WAITING_FOR_EVENT;
}
중복은 문제.
return
문이 하나break
, continue
, goto
사용 Xreturn
, break
, continue
를 여러 차례 사용하게 됨goto
문 : 큰 함수에서만 의미 있음, 작은 함수에서는 피할 것class SetUpTeardownIncluder{
constructor(pageData, isSuite, testPage, newPageContent, pageCrawler) {
this.pageData = 0; // PageData type
this.isSuite = 0; // bool type
this.testPage = 0; // WikiPage type
this.newPageContent = "";
this.pageCrawler = 0; // PageCrawler type
}
SetUpTearDownIncluder(){
this.pageData = 0 // PageData type
this.isSuite = 0 // bool type
this.testPage = 0 // WikiPage type
this.newPageContent = ""
this.pageCrawler = 0 // PageCrawler type
}
render(pageData){
return this.render(pageData, false);
}
render(pageData, isSuite){
return this.SetupTeardownIncluder(pageData).render(isSuite);
}
setUpTearDownIncluder(pageData){
this.pageData = pageData;
this.testPage = pageData.getWikiPage();
this.pageCrawler = testPage.getPageCrawler();
this.newPageContent = "";
}
render(isSuite)
this.isSuite = isSuite;
if (this.isTestPage())
this.includeSetupAndTeardownPages();
return this.pageData.getHtml();
isTestPage(){
return this.pageData.hasAttribute("Test");
}
includeSetupAndTeardownPages(){
this.includeSetupPages();
this.includePageContent();
this.includeTeardownPages();
this.updatPageContent();
}
includeSetupPages(){
if this.isSuite{
this.includeSuiteSetupPage();
this.includeSetupPage();
}
}
includeSuiteSetupPage(){
this.include(SuiteResponder.SUITE_SETUP_NAME, "-setup");
}
includeSetupPage(){
this.include("SetUp", "-setup");
}
includePageContent(){
this.newPageContent.append(pageData.getContent())
}
includeTeardownPages(this){
this.includeTearDownPage();
if (this.isSuite)
this.includeSuiteTeardownPage();
}
includeTeardownPage(this){
this.include("TearDown", "-teardown");
}
includeSuiteTeardownPage(this){
this.include(SuiteResponder.SUITE_TEARDOWN_NAME, "-teardown");
}
updatePageContent(){
this.pageData.setContent(str(newPageContent));
}
include(pageName,arg){
this.inheritedPage = this.findInheritedPage(pageName);
if (this.inheritedPage != NULL){
this.pagePathName = this.getPathNameForPage(this.inheritedPage);
this.buildIncludeDirective(this.pagePathName, arg);
}
}
findInheritedPage(pageName){
return this.PageCrawlerImpl.getInheritedPage(pageName, this.testPage);
}
getPathNameForPage(page){
this.pagePath = this.pageCrawler.getFullPath(page);
return this.PathParser.render(this.pagePath);
}
buildIncludeDirective(pagePathName, arg){
this.newPageContent.append("\n!include ");
this.newPageContent.append(arg);
this.newPageContent.append(" .");
this.newPageContent.append(pagePathName);
this.newPageContent.append("\n");
}
}