단순해진 깨끗한 코드 - 1부

몇 년 전에 Robert C. MartinClean Code book을 읽었습니다. 특히 경력의 주니어 년에 있는 사람들을 위한 훌륭한 책입니다. 보다 성숙한 소프트웨어 개발자/엔지니어가 되고 양질의 코드를 더 자주 작성하는 데 도움이 됩니다.

이 책에서 배운 팁, 요령 및 사례를 모아서 여러 부분으로 게시할 예정입니다.

Note that there are quotes and code examples that I’ve used from the original book in this series.



더 이상 고민하지 않고 시작하겠습니다.

#1 짧은 함수가 더 좋습니다

밥 아저씨가 함수의 길이에 대해 말하는 것은 함수가 작을수록 좋다는 것입니다.
그는 if 문, else 문 및 while 문 내의 코드 블록이 한 줄이어야 한다고 제안합니다. 또한 함수의 들여쓰기 수준은 1 또는 2를 넘지 않아야 합니다.

할 수 있다면 좋은 습관입니다. 이 규칙을 항상 엄격하게 준수할 수는 없지만 가능한 한 많이 하려고 노력하십시오.

#2 가능한 경우 인스턴스 변수를 도입하십시오.

기본 매개변수 대신 인스턴스 변수를 함수에 전달하는 것은 적절할 때 좋은 생각입니다. 하지만 언제가 적당할까요?

내부에 많은 변수가 선언된 큰 함수를 고려하십시오. 해당 함수의 작은 부분을 별도의 함수로 추출하고 싶다고 가정해 보겠습니다. 그러나 추출하려는 코드는 함수에 선언된 변수 중 4개를 사용합니다.

이것은 인스턴스 변수를 전달하는 것이 좋은 생각일 수 있는 상황입니다.

class Sample 
{ 
  public function __construct() 
  {

  }

  public function render() 
  {
   // doing some logic
   $this->getHtml($var1, $var2, $var3, $var4);
  }

  private function getHtml($var1, $var2, $var3, $var4) 
  {
   // doing some logic with vars
  }
}


될 것입니다:

class Sample 
{
  private $var;

  public function __construct(VarThing $var) 
  {
   $this->var = $var;  
  }

  public function render() 
  {
   // doing some logic
   $this->getHtml();
  }

  private function getHtml() 
  {
   // $this->var is accessible here
  }
}



#3 BUILD-OPERAATE-CHECK 패턴
Build-Operate-Check 패턴과 유사한 Arrange-Act-Assert 또는 AAA 패턴에 대해 들어본 적이 있을 것입니다.

다음 코드를 어떻게 개선할 수 있다고 생각하십니까?

public function testGetPageHieratchyAsXml()
{
    crawler()->addPage($root, PathParser()->parse("PageOne"));
    crawler()->addPage($root, 
       PathParser()->parse("PageOne.ChildOne"));
    crawler()->addPage($root, PathParser()->parse("PageTwo"));

    request()->setResource("root");
    request()->addInput("type", "pages");

    $responder = new SerializedPageResponder();
    $response = $responder->makeResponse(
        new FitNesseContext($root), $request
    );

    $xml = $response->getContent();

    assertEquals("text/xml", $response->getContentType());
    assertSubString("<name>PageOne</name>", $xml);
    assertSubString("<name>PageTwo</name>", $xml);
    assertSubString("<name>ChildOne</name>", $xml);
}

public function testGetPageHieratchyAsXmlDoesntContainSymbolicLinks()
{

    $pageOne = crawler()->addPage($root, 
       PathParser()->parse("PageOne"));

    crawler()->addPage($root, 
       PathParser()->parse("PageOne.ChildOne"));
    crawler()->addPage($root, PathParser()->parse("PageTwo"));

    $data = $pageOne->getData();

    $properties = $data->getProperties();
    $symLinks = $properties->set(SymbolicPage::PROPERTY_NAME);

    $symLinks.set("SymPage", "PageTwo");
    $pageOne.commit($data);
    request()->setResource("root");
    request()->addInput("type", "pages");

    $responder = new SerializedPageResponder();
    $response = $responder->makeResponse(
        new FitNesseContext($root), $request);

    $xml = $response->getContent();

    assertEquals("text/xml", $response->getContentType());
    assertSubString("<name>PageOne</name>", $xml);
    assertSubString("<name>PageTwo</name>", $xml);
    assertSubString("<name>ChildOne</name>", $xml);
    assertNotSubString("SymPage", $xml);
}

public function testGetDataAsHtml()
{
    crawler()->addPage($root, PathParser()
           ->parse("TestPageOne"), "test page");
    request()->setResource("TestPageOne");
    request()->addInput("type", "data");
    $responder = new SerializedPageResponder();
    $response = $responder->makeResponse(
        new FitNesseContext($root), $request);
    $xml = $response->getContent();
    assertEquals("text/xml", $response->getContentType());
    assertSubString("test page", $xml);
    assertSubString("<Test", $xml);
}


개선할 수 있는 방법은 다음과 같습니다.

public function testGetPageHierarchyAsXml()
{
    makePages("PageOne", "PageOne.ChildOne", "PageTwo");
    submitRequest("root", "type:pages");
    assertResponseIsXML();
    assertResponseContains("<name>PageOne</name>", "<name>PageTwo</name>", "<name>ChildOne</name>");
}

public function testSymbolicLinksAreNotInXmlPageHierarchy() 
{
    $page = makePage("PageOne");
    makePages("PageOne.ChildOne", "PageTwo");
    addLinkTo($page, "PageTwo", "SymPage");
    submitRequest("root", "type:pages");
    assertResponseIsXML();
    assertResponseContains("<name>PageOne</name>", "<name>PageTwo</name>", "<name>ChildOne</name>");
    assertResponseDoesNotContain("SymPage");
}

public function testGetDataAsXml()
{
    makePageWithContent("TestPageOne", "test page");
    submitRequest("TestPageOne", "type:data");
    assertResponseIsXML();
    assertResponseContains("test page", "<Test");
}


이 패턴의 이점은 대부분의 성가신 세부 사항이 제거되었다는 것입니다. 테스트는 핵심을 정확히 파악하고 실제로 필요한 데이터 유형과 기능만 사용합니다. 이 테스트를 읽는 사람은 누구나 세부 사항에 현혹되거나 압도당하지 않고 테스트가 수행하는 작업을 매우 빠르게 이해할 수 있어야 합니다.

#4 단일 책임 및 오류 처리

이것을 설명하는 가장 좋은 방법은 책에서 직접 인용하는 것입니다.

Functions should do one thing. Error handing is one thing. Thus, a function that handles errors should do nothing else. This implies that if the keyword try exists in a function, it should be the very first word in the function and that there should be nothing after the catch/finally.



#5 더 나은 오류 처리

이 코드 조각이 어떻게 더 잘 형성될 수 있다고 생각하십니까?

$port = new ACMEPort(12);
try {
    $port->open();
} catch (DeviceResponseException $e) {
        reportPortError($e);
        logger()->log("Device response exception", $e);
    } catch (ATM1212UnlockedException $e) {
        reportPortError($e);
        logger()->log("Unlock exception", $e);
    } catch (GMXError $e) {
        reportPortError($e);
        logger()->log("Device response exception");
    } finally {
        /// ...
}


호출하는 API를 래핑하고 일반적인 예외 유형을 반환하도록 하여 코드를 상당히 단순화할 수 있습니다.

$port = new LocalPort(12);

try {
    $port->open();
} catch (PortDeviceFailure $e) {
    reportError($e);
    logger()->log($e->getMessage(), $e);
} finally {
    // ...
}
class LocalPort {

    private $innerPort;

    public function __construct(int $portNumber) {
        $this->innerPort = new ACMEPort($portNumber);
    }

    public function open() {
        try {
            $this->innerPort->open();
        } catch (DeviceResponseException $e) {
            throw new PortDeviceFailure($e);
        } catch (ATM1212UnlockedException $e) {
            throw new PortDeviceFailure($e);
        } catch (GMXError $e) {
            throw new PortDeviceFailure($e);
        }
    }

    // ...
}


ACMEPort에 대해 정의한 것과 같은 래퍼는 매우 유용할 수 있습니다.

괜찮아. 이 부분에 대한 내용입니다. 곧 다음편도 계속 올리겠습니다. 내Telegram 채널에 가입하면 최신 게시물에 대한 알림을 받을 수 있습니다. 또한 및 에서 나를 팔로우할 수 있습니다.

좋은 웹페이지 즐겨찾기