• 어제는 집에서 쉬는건데.. 민형옹, 승훈옹, 다운이, 소미랑 마시는게 아니었쓰 ㅋ 2008-01-19 13:41:59
  • 계용옹 지금부터는 유부남이겠네. ㅋ 결혼식도 못가보고 ㅋ 2008-01-19 13:42:14
  • 히어로즈 시즌 1 완료. 어렵다. 뭐가 이리도. 중간중간에 자막 없는 편이 있어서 그런지 2008-01-19 13:44:07

이 글은 bmwe3님의 2008년 1월 19일의 미투데이 내용입니다.

by 무위자연 2008. 1. 20. 04:34

설계는  tradeoff와 우선순위에 관한 것이다.

설계는 제약이 따른다.

설계는 발견적 학습과정이다.

설계는 창발적이다.(갑작스럽게 튀어나오는)

전체 프로그램을 한번에 구성하는 것이 아니라 한 부분을 집중할수 있게 프로그램을 구성하도록 해야 한다.

최종목표는 한번에 생각해야 하는 프로그램의 양을 최소화하는 것이다.

복잡한 하나도나 여러개의 간반한 정보를 이해하는 것이 빠르다.

바람직한 설계의 특징

  • 복잡성 최소화
  • 유지관리의 편리함
  • 느슨한 결합
  • 확장성
  • 재사용성
  • 높은 fan-in 주어진 클래스를 사용하는 클래스의 수가 많음을 의미한다
  • 낮은fan-out 주어진 클래스가 다른 클래스를 적게 사용하는 것을 의미한다.
  • 이식성
  • 간결성
  • 계층성
  • 표준기술들

 

설계를 시작하기 전에 서브시스템 간의 커뮤니케이션은 "알아야 할 필요성" 이 있을때에만 가능하도록 한다. 그런 연후에 차츰 그 제한을 풀어가는 쪽으로 바꾸어야 한다.서브시스템간의 커뮤니케이션을 모두 가능하게 시작하여 제한하기 시작할 경우에 너무 많은 노력과 부작용이 발생한다.

공통적인 서브시스템

  • 비지니스 규칙 - 시스템에 입력하는 모든 자료
  • 사용자 인터페이스 - 사용자 인터페이스를 독립적으로 작성한 서브시스템은 프로그램의 나머지 부분에 영향을 받지 않고 발전할수 있다
  • 데이터베이스 접근 - 데이터베이스 연산을 한 곳에 집중시킬수 있고 데이터를 다룰 때 오류가 발생할 가능성도 줄인다.

    객체와 클래스. 스키마와 인스턴스

    설계수준

1수준 소프트웨어 수준

2수준 서브시스템이나 패키지로 분할

3수준 클래스로 분할

4수준 루틴으로 분할

5수준 내부 루틴 설계 : 설계는 의사코드를 작성하고 참고서적에서 알고리즘을 살펴보고 루틴 내의 코드 단락을 어떻게 구성할 것인지를 결정하고 프로그래밍 언어로 코드를 작성하는 활동으로 구성된다.

 

-좋은 class interface는 해당 클래스의 내부적인 작업에는 신경 쓸 필요없이 인터페이스에 집중할수 있는 추상화이다.

-추상화를 통해서 얻을수 있는 주요한 이득은 관련없는 세부사항들을 무시할수 있다는 것이다.

-객체들 간의 유사성과 차이점을 정의하는 것을 "상속"이라고 부른다. 왜나하면 특정한 정규직과 계약직 직원은 일반적인 직원타입의 특성을 상속받기때문이다.상속의 이득은 추상화 개념과 시너지 효과를 갖는다는 점이다. 추상화는 서로 다른 수준에서 객체를 다룬다. 상속은 프로그래밍을 단순화시킨다. 예를 들어 문(door)에 대한 일반적인 특성에 의존하는 일반적인 루틴을 작성한 다음에 특정한 종류의 문에 대한 특정한 연산을 처리하는 특정한 루틴을 작성할 수 있다. 이런 연산을 지원하기 위한 능력이 "다형성" 이다.

-정보은닉은 복잡성을 감추는 능력을 가지는 것이다. 클래스 인터페이스를 벗어나는 범위에 영향을 끼쳐서는 안된다. 클래스를 설계하는데 있어서 중요한 것은 어떤 기능들이 클래스외부에 알려야 하고, 어떤 기능들이 은닉되어야 하는지를 결정하는 것이다. 무엇을 숨겨야 한느지에 대한 질문은 모든 수준에서 좋은 설계결정에 도움을 준다.구현수준에서는 리터럴대신 명명된 상수를 사용하도록한다. 이것은 클래스 내부에서 좋은 루틴과 좋은 매개변수 일므을 생성하는데 도움을 준다. 또한, 시스템 수준에서는 클래스와 서브시스템의 분해 및 상호연결에 대한 결정에 도움을 준다. "내가 무엇을 숨겨야 하지?"란 의문을 늘 갖도록 한다.

변경될 가능성이 있는 것을 고립시키기 위한 3단계.

1.변경될 것처럼 보이는 항목들을 규명한다

2.변경될 것같은 항목을 분류한다

3.변경될 것처럼 보이는 항목을 고립시킨다.

변할 것 같은 영역들

비지니스 규칙 - 가장 빈번한 소프트웨어 변경의 원인. 고립시켜야 한다

하드웨어 의존성 - 하드웨어에 의존하는 서브시스템과 클래스만 고립시킨다

입려과 출력

비표준언어기능

어려운설계와 구현부분.

상태변수 - 프로그램의 상태를 가릨고 다른 데이터들보다 자주 변경되는 경향이 있다. 상태변수를 사용하는데 있어서 유의점

> boolean변수를 상태변수처럼 사용하지 말라. 대신 열거형을 사용하라!!!!상태변수에서 새로운 상태를 추가하는 일은 흔히 발생하며, 열거형에 새로운 타입을 추가하면 변수를 검사하는 모든 코드를 검토하는 대신 컴파일만 다시 하면 된다.

> 변수를 직접적으로 접근하는 대신 접근 루틴을 사용하라! 변수 대신 접근 루틴을 검사하게 함으로써 보다 정교하게 상태를 검사할수 있다. 예를 들어, 오류상태변수와 현재함수의 상태 변수 조합을 검사하고 싶을때, 만약 테스트가 루틴에 숨겨져 있다면 그 작업이 쉬워지겠지만, 프로그램 전체에 걸쳐서 복잡한 테스트가 입력되어 있다면 작업이 어려워 질 것이다.

  • 느슨한 결합을 유지하라. coupling은 클래스나 루틴이 다른 클래스나 루틴과 얼마나 밀접하게 연관되어 있는지를 기술한다. 다른 클래스와 루틴보다 작고, 직접적이며, 눈에 띄고, 유연한 관계를 갖는 클래스와 루틴을 생성하는 것이 목표이며, 이를 "loose coupling"이라 한다.결합의 기준은

    크기. 가시성, 유연성 등이 있다. 느슨한 결합의 핵심은 효율적인 모듈이 추가적 추상화 수준을 제공한다는 점이다. 이는 전체적인 프로그램의 복잡성을 줄이고 한번에 한가지에 집중할 수 있도록 한다. 하지만 만약 어떤 모듈을 사용하기 위해서 한번에 하나 이상의 것을(내부 작업에 대한 이해, 전역데이터의 수정, 불확실한 기능)에 집중해야 한다면, 추상적인 효과는 사라지고 복잡성을 관리하는데 도움을 주기 위한 모듈의 능력은 줄어들거나 사라져버린다.

 

일반적으로 널리 사용되는 패턴을 찾아라.

  • 패턴은 이미 만들어진 추상화를 제공함으로써 복장성을 줄인다
  • 패턴은 일반적으로 널리 사용되는 해결책의 세부사항들을 규정함으로써 오류를 줄인다
  • 패턴은 설계대안들을 제안함으로써 발견적 학습의 기치를 제공한다 - 내 설계문제가 적합한  패턴이 어떤 것이지? 대해 물을수 있고 잘 알고 있는 대안들을 살펴보는 것은 옷을 설계하기 위해서 디자인을 직접 만드는 것과는 비교할수 없을 만큼 쉽다. 그리고 익순한 패턴을 이용하여 작성한 코드는 완전히 직접 작성한 코드보다 다른 사람이 이해하기 더 쉽다.
  • 패턴은 설계 대화를 보다 높은 수준으로 옮김으로써 의사소통을 원할하게 한다.

    패턴을 사용함에 있어서 주의할 것은 패턴을 사용하기 위해 코드를 억지로 끼워맞추는 것이다. 그러다 보면 오히려 복잡성이 증가할수 있다.

 

설계방법

  • 반복
  • 분할정복 > 하향식 접근 방법에 대한 찬성론(Top down) - 분할정복 프로세스는 두가지 면이 반복적이다. 한가지 수준이 아닌 여러 수준에서의 분할이 이루어져야 하고, 어떻게 분해하는 것이 효율적인가를 반복적으로 고려해보아야 하기때문이다. 프로그램을 분해하는 것보다 코드 작성이 쉬워지는 단계까지 분해했다면 작업이 완료된다.

    >상향식 접근 방법에 대한 찬성론(Bottom up) - "이 시스템은 무엇을 해야 하는가?"에 대한 질문에 답할수 있는 보다 구체적인 클래스를 작성하는 것으로 시작한다. 여러가지 저 수준의 책임을 규명한 다음에 상위수준으로 올라가는 것이다.

설계작업기록하기

  • 설계문서를 코드 자체에 넣어라 - 최신으로 유지하기가 쉽다
  • 설계에 대한 논의와 결정을 wiki(혹은 그에 준하여 편집이 쉬운 웹게시물집합?)에 기록하라
  • e-mail로 요약하라 - 설계논의를 끝낸 후, 누군가가 설계에 대한 요약을 작성하여 프로젝트 팀원에게 메일을 보낸다. 그리고 메일의 복사본을 프로젝트 공용 이메일 폴더에 저장하라.
  • 디지털카메라를 사용하라 - 문서화에 한계가 있다. 다양한 방법은 기록의 지루함을 덜어준다
  • 설계플립차트를 보관하라
  • CRC(클래스, 책임, 협력자)카드를 사용하라.- 인덱스카드를 사용하는 것이다. 각각의 카드위에 설계자는 클래스의 이름, 클래스의 책임,그리고 협력자(해당 클래스와 협력하는 다른 클래스)를 기록한다. 그러면 설계 그룹은 그들이 좋은 설계를 작성했다는 것에 만족할때까지 그 카드로 작업한다. 그 시점이 되면, 나중에 참조하기 위하여 카드를 보관할 수 있다.
  • 적절한 상세수준에서 UML 다이어그램을 작성하라.

 

  • 요점정리 중에서.

    • 소프트웨어의 일차적인 기술적 의무는 복잡성을 관리하는 것이다. 이것은 단순함에 초점을 맞춘 설계의 도움을 받을수 있다
    • 단순함은 두 가지의 일반적인 방법으로 달성할수 있다. 누군가가 한번에 처리해야 하는 본질적인 복잡성의 양을 최소화하고 부수적인 복잡성이 불필요하게 증가하지 않도록 하는 것이다
    • 설계는 발견적 학습이다. 어떠한 단일 방법론에 독단적으로 집착하면 상상력과 프로그램에 해를 입는다
    • 좋은 설계는 반복적이다. 여러번 시도할수록, 마지막 설계는 더 좋아질 것이다
    • 정보은닉은 중요하다. "무엇을 숨겨야 하지?"라는 질문이 해결하기 어려운 설계 상의 문제들을 해결해준다
    • 설계에 대해서 더 많이 보고 공부해야 한다.!

이 글은 스프링노트에서 작성되었습니다.

by 무위자연 2008. 1. 19. 17:07

클래스는 응집력있고 잘정의된 긴으을 공유하는 데이터와 루틴의 모음이다.

 

추상데이터형(ADT) - 데이터와 데이터를 다루는 연산의 집합.

 

만약에 폰트를 제어한다면?

currentFont.size = 16  || currentFont.size = PointsToPixel(12) || currentFont.sizeInPixels = PointsToPixels(12) ... 크기를 제어하려고 해도 이렇게 부담스럽다

currentFont.bold = true. etc

ADT 사용시의 혜택

 

-세부적인 구현 사항을 감출수 있다.

-변경이 전체프로그램에 영향을 미치지 않는다 : 폰트에 보다 다양하고 많은 연산들 하고자 하면 한 곳에서 추가해주면 된다.

-인터페이스가 보다 많은 정보를 제공하도록 만들수 있다.:currentFont.size = 16과 같은 코드는 16이 픽셀단위인지 포인트단위인지 모호하다. 하지만 모아놓으면 의미                                                                              가 보다 명확해진다.

-성능을 향상시키기 쉽다 : 폰트의 성능을 향상시켜야 한다면, 프로그램 전체를 향상시키기보다 잘 정의된 루틴을 재작성할수 있다

-외관상으로 프로그램이 정확하다는 것을 더 잘 알 것이다. currentFont.attribute = 0x02 식으로 쓴다면 값이 정확한지 입력이 된 것인지 동작이 제대로 된 것인지 판단하기 어렵다.

-프로그램이 보다 더 스스로를 설명하게 된다. : 현재 사용하는 변수나 이름 입력값에 대한 정의가 이루어져 있어 보다 의미가 명확해진다

-프로그램에 모든 데이터를 넘길 필요가 없다

-저수준 구현 구조체 대신 실세계의 개체들을 다룰수 있다

ADT사용의 예제

currentFont.SetSizeInPoints(sizeInPoints)

currentFont.SetSizeInPixels(sizeInPixels)

currentFont.SetBoldOn()

currentFont.SetBoldOff()

currentFont.SetItalicOn()

currentFont.SetItalicOff()

 

좋은 추상화

클래스 인터페이스에서 일관된 추상화수준을 표현한다.

 

클래스가 구현하고 있는 추상화가 무엇인지 확실하게 이해하도록 하라.

 

서로 반대되는 기능을 갖는 서비스 쌍(pair)을 제공하라. : 클래스를 설계할때 각 public 루틴들에 반대되는 기능이 필요한지를 검사한다. 불필요하게 반대되는 기능을 만들어서는 안되지만, 필요한지는 확인하도록 한다

 

 

관련없는 정보를 다른 클래스로 이동시켜라

 

가능하다면, 인터페이스를 의미론적이기보다는 프로그래밍적으로 만들어라.:루틴 A는 루틴B 전에 호출되어야 한다 혹은 데이타1은 루틴A에 입력되기 전에 초기화 되어야 한다는 류의 의미보다는 프로그래밍 언어로 표현하고자 해야 한다.

 

수정시 인터페이스의 추상화가 손상되는 것을 조심하라

 

인터페이스 추상화와 어긋나는 public 멤버를 추가하지 말라.

 

추상화와 응집도를 함께 고려해라.

 

좋은캡슐화

추상화는 세부적인 구현 사항을 무시할수 있는 모델을 제공함으로서 복잡성 관리에 도움을 준다. 반면에 캡슐화는 세부사항을 알고 싶어 할때에도 이를 차단해버리는 경향이 있다.추상화와 캡슐화는 둘다 있던지 없던지 간에 하나만 있어서는 의미가 없다.

 

클래스와 멤버에 대한 접근성을 최소화하라 : 인터페이스 추상화와 무결성을 가장 잘 유지할수 있는 가에 따라 제한자를 구별해서 사용한다. 만약 노출시키는 루틴이 추상화와 일관성이 있다면 노출시켜도 별 문제가 없다. 먄약 확신할수 없다면, 일반적으로 숨기는 것이 숨기지 않는 것보다 좋다.

 

멤버데이터를 public으로 노출시키지 마라. : 멤버변수(멤버데이터)는 노출시키지 말고 멤버변수를 제어하고자 하는 멤버변수는 노출시켜라.

 

내부의 세부적인 구현 사항들을 클래스의 인터페이스에 입력하지 않는다 : 세부구현 사항들을 노출하는 코드를 읽을때, 구현단서를 찾기 위해서 클래스 인터페이스의 private섹션은 뒤지는 충동을 억제할수 있어야 한다

 

클래스 사용자들을 추측하지 마라 : 클래스는 클래스 인터페이스에 수반되어 있는 계약대로 설계되고 구현되어야 한다. 인터페이스 문서에 적혀 있는 것 이외에는 인터페이스가 어떻게 사용될 것인지에 대해서 추측해서 안된다. (그래서 더욱 애러처리에 신경써줘야 한다)

 

friend 클래스를 피하라

 

루틴이 public 루틴만 사용한다고 해서 public 인터페이스에 놓지마라.: 누가 이 노출된 루틴에 접근을 시도할수 있다

 

코드를 작성할때의 편의성보다 읽을때의 편의성을 추구하라.

 

캡슐화의 의미론적인 위반을 매우.매우! 주의하라

 

지나치케 밀접한 결합을 주의한다. : 일반적으로 결합은 느슨할수록 좋다. 다음은 가이드라인이다.

  • 클래스와 멤버의 접근성을 최소화하라
  • 프렌드 클래스는 밀접하게 결합되기때문에 피하라
  • 파생클래스가 기본 클래와 느슨하게 연결되도록, 기본 클래의 데이터를 protected보다 private로 선언하라
  • 클래스의 public인터페이스에서 멤버데이터를 노출하지 마라
  • 캡슐화의 의미론적 위반을 경계하라
  • "데이테르의 법칙"을 준수하라

 

포함("has a"관계)

예를 들어 직원은 이름을 가지며(has a) 전화번호를 가지며(has a) 세금ID를 갖는다(has a)

 

상속("is a"관계)

상속은 한 클래스가 다른 클래스를 특수화한다는 개념이다. 상속의 목적은 두 개 이상의 파생클래스에서 공통적으로 사용되는 요소들을 지정하는 기본클래스를 정의하여, 보다 간단한 코드를 작성하기 위함이다. 공통적인 요소들은 루틴 인터페이스, 구현부, 데이터멤버, 또는 데이터형이 될 수 있다.

public 상속을 통해서 "is a"를 구현하라

상속을 설계하고 설명하라. 그렇지 않으면 상속을 하지마라

Liskov치환원리(LSP, Liskov Substitutuin Principle)를 따르라 Barbara Liskov는 파생클래스가 기본 클래스의 정확히 특수화된 버전이 아니라면("is a"), 기본클래스로부터 상속받아서는 안된다고 주장했다. 서브클래스는 사용자가 그 차이점을 알 필요 없이 기본 클래스의 인터페이스를 통해서 사용가능해야 한다.

상속받고 싶을때만 상속받도록 한다. : 어떤 것을 상속시키고 시키지 않을지를 결정하라

오버라이드가 불가능한 멤버루틴을 오버라이드하지 마라.

공통적으로 사용되는 인터페이스, 데이터, 행위(behaviour)를 상속 트리에서 가능한 한 가장 높은 곳으로 이동시켜라.

인스턴스가 하나뿐인 클래스를 의심하라 - 싱글톤은 예외상황이다

파생클래스가 하나뿐인 기본 클래스를 의심해라.

루틴을 오버라이드하고 파생된 루틴내부에서 아무것도 하지 않는 클래스들을 의심하라.

깊은 상속트리를 피하라. - 깊은 상속트리를 복잡성을 증가시키고, 오류율의 증가와도 연관이 있다. 중복된 코드를 피하고 복잡성을 최소화하기 위해서 상속을 사용하고 있는지 확인하라.

광범위한 형 검사보다 다형성을 택하라.: 항상 그렇지는 않지만, 때떄로 자주 반복되는 case문은 상속이 더 좋은 설계일수도 있다.

ex 다형성으로 대체되어야 할 것 같은 case문을 C++로 작성한 예제.

switch(shape.type)

{

 case Shape_Circle:

         shape.DrawCircle();

        break;

case Shape_Square:

       shape.DrawSquare();

       break;

...

}

이 예제에서의 shape.DrawCircle();,  shape.DrawSquare();는 shape.Draw()와 같은 단일 루틴으로 대체되어야 한다.

모든데이터를 protected가 아닌 private으로 만들어라. 만약 정말로 파생클래스에서 기본클래스의 특성을 접근해야 한다면, protected 접근 함수를 대신 제공해라.

다중상속 - 강력하나 위험하다. 다이아몬드 상속문제를 야기할수 있다.

 

멤버함수의 데이터.

클래스에 가능한 한 적은 수의 루틴을 유지하라.

여러분이 원하지 않는 멤버함수와 연산자들이 암시적으로 생성되지 않도록 하라

클래스에서 호출되는 루틴의 수를 최소화하라

다른 클래스에 대한 간접 루틴호출을 최소화하라. - 직접적인 연결은 매우 위험하다. account.ContactPerson().DaytimeContactInfo().PhoneNumber()와 같은 간접적인 연결은 훨씬 위험하다. 연구자들은 "데미테르의 법칙"을 고안했다. 이 규칙의 핵심은 객체A가 자신의 루틴은 어느 것이든 호출할수 있다. 만약 객체A가 객체 B를 인스턴스화했다면, 객체B의 루틴은 어느 것이든 호출할수 있다. 하지만 객체 B에 의해서 제공되는 객체들을 호출하지 않아야 한다. 앞에서 살펴본 Account예제라면, account.ContactPerson()은 문제 없지만, account.ContactPerson().DaytimeContactInfo()는 문제가 있다는 것이다.

일반적으로 클래스가 다른 클래스와 협력하는 정도를 최소화하라.

 

생성자

가능하면 모든 멤버데이터를 모든 생성자에서 초기화한다. - 모든 데이터 멤버들을 모든 생성자에서 초기화하는 것은 비싸지 않은 방어적인 프로그래밍 습관이다

private생성자를 사용하여 싱글톤 속성을 구현하라. - 오직 하나의 객체만 인스턴스화되는 것을 허용하려면 클래스의 모든 생성자를 숨기고 클래스의 단일 인스턴스에 접근하기 위한 static GetInstance()루틴을 제공하는 방법으로 구현할수있다.

ex. private 생성자로 싱글톤을 구현한 자바 예제

public class MaxId{

   private MaxId(){} //private 생성자

   public static MaxId GetInstance(){ return m_instance; } //단일 인스턴스에 대한 접근을 제공하는 public 루틴

    private static final MaxId m_instance = new MaxId();

}

다른 사항들이 증명될때까지 얕은 복사보다 깊은 복사를 택하라

 

클래스를 작성하는 이유.

실세계의 객체를 모델링하라

추상 객체들을 모델링하라

복잡성을 줄여라

복잡성을 고립시켜라

세부적인 정보를 숨겨라

변경의 영향을 제한하라

전역데이터를 숨겨라

매개변수의 전달을 간소화하라

중앙집중 관리하라

코드의 재사용성을 도와라 - 코드의 재사용성은 프로젝트가 반복되고 심화될수록 빛을 바란다. 다만, "재사용성을 위한 설계작업"을 하지 않아야 한다. 그래야 불필요한 복잡성 증가를 막을수 있고 진정한 캡슐화와 정보은닉이 쉬워진다.

프로그램군(family)를 위한 계획을 작성하라

연관된 기능을 패키지화하라

특정한 리팩토링을 수행하라

 

피해야할 클래스

만능(god)클래스를 생성하지 마라

관련이 없는 클래스를 제거해라

 

6장의 요점

  • 클래스의 인터페이스는 일관성이 있는 추상화를 제공해야 한다. 많은 문제들이 이 규칙을 어기기떄문에 발생한다
  • 클래스 인터페이스는 시스템 인터페이스나 설계 결정, 또는 세부적인 구현 사항들을 숨겨야 한다
  • 만약 "is a"관계를 모델링하고 있지 않는다면, 일반적으로 상속보다 포함을 선택해야 한다
  • 상속은 유용한 도구이지만 복잡성을 증가시키며, 이는 복잡성을 관리해야 하는 소프트웨어의 일차적인 기술적 의미에 반대된다
  • 클래스는 복잡성을 관리하기 위해서 여러분이 사용할수 있는 일차적인 도구이다. 그러한 목표를 달성할수 있도록 설계에 많은 주의를 기울여야 한다.

 

 

이 글은 스프링노트에서 작성되었습니다.

by 무위자연 2008. 1. 19. 17:07

이해하기 쉬운 중간 단계의 추상화를 도입한다

ex. if(node != NULL) then

      while(node.next != NULL) do

node = node.next;

leafName = node.name;

 end while

       else

      leadNAme = ""

end if

위와 같은 루틴을 다음의 명령어로 바꿀수 있다.

leafName = GetLeafName(node)

이는 코드를 더욱 읽기 쉽고 이해하기 쉽게 만든다. 또한, 원래 코드를 포함하고 있는 루틴내에서 복잡성을 줄여준다

 

코드의 중복을 피한다 - 루틴을 작성하는 가장 큰 이유가 코드의 중복을 피하는 것이다. 두 루틴에서 중복된 코드를 빼내어, 공통된 코드의 일반화된 버전을 기본 클래스에 넣은 다음 두개의 특화된 루틴들을 서브클래스로 이동시킨다. 또는 공통된 코드를 별도의 루틴으로 작성한 다음, 두 루틴에 읩력된 부분을 호출하도록 할수도 있다. 한곳에 코드가 있으면 중복을 줄이고 공간을 절약하고 수정도 용이해진다.

 

서브클래싱을 지원한다. - 오버라이드가 가능한 루틴을 간단하게 유지한다면, 서브 클래스 구현에서 오류가 발생할 확률도 줄일수 있다

 

순서를 숨긴다

 

이식성을 향상시킨다.

 

복잡한 boolean 테스트를 단순화한다.

 

성능을 개선한다 - 여러 곳에 있는 코드 대신에 한곳에 있는 코드를 최적화시킨다. 한 곳에 모아놓으면 비효율적인 부분을 개선하기 쉬워진다.

 

모든 루틴들이 작다는 것을 보장하기 위해서 루틴을 작성하는 것은 아니다. 문제는 어떻게 알고리즘을 구성하는가 이지 얼마나 코드가 짧은가와는 상관없는 일이다.

 

루틴을 작성하기에는 너무 단순해보이는 연산

효율적인 루틴을 작성하는데 가장 큰 정신적 장애물 중 하나는 간단한 목적을 위해서 간단한 루틴을 작성하는 것을 꺼리는 마음이다. 필자의 경험상 2-3줄의 짧은 루틴도 매우 유용하다. 루틴의 장점중에 하나는 가독성을 높이는 것이다.

ex. 계산을 수행하는 의사코드 예제.

points = deviceUnits * (POINTS_PER_INCH) / DeviceUnitsPerInch())

함수로 변환된 계산을 의사코드로 작성한 예제

function DeviceUnitsToPoints (deviceUnits Integer) : Integer

DeviceUnitsToPoints = deviceUnits * (POINTS_PER_INCH / DeviceUnitsPerInch())

end function

계산함수를 호출하는 의사코드

points = DeviceUnitsToPoints(deviceUnits)

여기서 특정환경에서 DeviceUnitsPerInch는 0을 리턴해야 한다면 함수만 수정해주면 된다 아래처럼.

function DeviceUnitsToPoints (deviceUnits Integer) : Integer

if(DeviceUnitsPerInch != 0)

DeviceUnitsToPoints = deviceUnits * (POINTS_PER_INCH / DeviceUnitsPerInch())

else

DeviceUnitsToPoints = 0;

endif

end function

만일 이 코드를 12곳에서 사용했다면 3 * 12만큼 수정 할 것을 3줄로 줄여준 것이다.

 

루틴수준에서의 설계.

루틴내에서 혹은 루틴간에 응집성이 있어야 한다. 순차적 응집성. 통신적 응집성. 일시적 응집성 등을 고려해야 한다. 한 루틴이 if와 case문의 연속으로 구성되어 있고 각 분기문에서 명령어 처리만 하는 경우에 기술적인 용어로 "event handler"라고 한다.

 

좋은 루틴 이름

루틴의 좋은 이름은 루틴이 하는 모든 것을 분명하게 해준다.

루틴이 하는 모든것을 표현하라. - 예를 들어 전체보고서를 계산하고 출력파일을 연다면 computeReportTotal()이 아니라 ComputeReportTotalsAndOpenOutputFile()이 적절한 이름이 되겠다. 다만 한번에 보다 직접적인 일을 하는 루틴을 작성하는 것이 좋다

의미가 없거나 모호하러나 뚜렷한 특징이 없는 동사를 피해라 - 어떤 동사들은 포괄적이고 유연해서 너무 많은 뜻을 포함하기도 한다. HandleCalcualtion(), PerfomService(), OutputUser(), ProcessInput() etc. 루틴의 역할이 모호하여 이름또한 모호한 경우 루틴을 재구성해서 뚜렷한 목적과 이름을 붙여준다.

루틴의 이름을 숫자만으로 구분하지 마라.

필요한 길이만큼 루틴의 이름을 만들어라.- 이름은 너무 길어도 너무 짧아도 루틴을 정확하게 이해하기 힘들다

함수의 이름을 지을때, 리턴값에 대한 설명을 사용해라.

프러시저의 이름을 지을때, 확실한 의미를 갖는 동사 다음에 객체를 사용해라. - 프로시저의 이름은 프로시저가 무엇을 하는지를 반영해야 하기때문에, 객체에 대한 연산은 동사 + 객체이름을 갖는다. ex.PrintDocument(), CalcMonthlyRevenues(), CheckOrderInfo() etc.

반의어를 정확하게 사용하라. first/last 와 같이 서로 반대되는 짝들은 쉽게 이해가 된다.

ex. 널리 쓰이는 반의어

add/remove   increment/decrement   open/close   begin/end   insert/delete   show/hide   create/destroy   lokc/unlock   source/target   first/last

min/max   start/stop   get/put   next/previous   up/down   get/set   old/new

공통적인 연산을 위한 규약을 만들어라.

 

루틴길이에 대한 문제.

정도의 차이는 있겠지만 200줄은 넘기지 않는 것이 좋을 듯하더이다.

 

루틴의 매개변수를 사용하는 방법

매개변수를 입력-수정-출력 순서로 입력한다. : 매개변수를 무작위로 혹은 알파벳순으로 정렬하는 대신, 입력만 가능한 것을 첫번째로, 입출력이 가능한 것을 두번째로, 출력만 가능한 것을 세번째로 나열한다.  - 순서는 필자에게 유용한 순서규약이고 각자가 자신에게 가장 명확한 규약을 만들어 써도 괜찮다

고유한 in과 out키워드 생성을 고려해보라.: 이런 키워드를 사용한다면 좀더 의미가 명확해 질수 있다.

만약 여러 루틴들이 유사한 매개변수들을 사용한다면, 유사한 매개변수들을 일관된 순서로 입력하라 : 루틴 매개변수의 순서는 기억에 도움이 된다. 예를 들어 fprintf()는 printf()와 첫번째 인자가 파일이라는 것을 제외하고는 아주 유사하다. fputs()은 마지막 인자로 파일을 추가한다는 점을 제외하고 puts()과 동일하다

모든 매개변수들을 사용하라 - 입력받은 매개변수중에 사용하지 않은 변수는 인터페이스에서 제거해버려라.

상태나 오류변수를 마지막에 입력한다. - 오류 역시 출력만 하는 변수임으로 마지막에 위치하는 것이 좋다

루틴의 매개변수를 작업용(working) 변수로 사ㅓ용하지 말라. - 루틴에 전달된 매개변수들을 작업용 변수로 사용하는 것은 좋지 않다.

ex. 입력매개변수를 부적절하게 사용하는 자바예제

int Sample(int inputVal)

{

inputVal = inputVal * CurrentMultiplier(inputVal);

inputVal = iinputVal + CurrentAdder(inputVal);

//...

return inputVal;      //이미 inputVal은 입력된 값을 가지고 있지 않다!!!

}

 

ex. 입력매개변수를 적절하게 사용하는 자바예제

int Sample(int inputVal)

{

int workingVal = inputVal;

workingVal = workingVal * CurrentMultiplier(workingVal );

workingVal = workingVal + CurrentAdder(workingVal );

//...중간에 어떤 상황에서도 inputVal의 값을 이용할수 있게 된다.

return workingVal ;     

}

매개변수에 대한 인터페이스 가정을 문서화하라.

루틴 매개변수의 수를 7개정도로 제한하라 : 7은 사람들의 이해력에 대한 매직넘버이다.

매개변수에서 사용할 입력, 수정, 출력 이름 규약을 고려하라. - 입력, 수정, 출력 매개변수를 구분하는 것이 중요하다면 그것을 식별할수 있는 식별자를 두어라.

ex.i_, m_ , o_ , 혹은 imput_ , modify_ , output_ , etc.

루틴이 인터페이스 추상화를 유지해야 할 필요가 있는 변수나 객체를 전달하라.

이름(named)매개변수를 사용하라 - 잘 모름-

실질적인 매개변수가 형식적인 매개변수와 일치하는지 확인하라

 

함수사용시 특별히 고려해야 할 사항들

함수를 사용할때와 프로시저를 사용할때를 구별하라 - 함수는 입력 매개변수들만을 취해서 함수자체를 통해 오직 하나의 리턴값을 가진다. 반면에 프로지서는 입력, 수정, 출력 매개변수들을 원하는 만큼 취해서 상태값을 바꿔준다. 루틴의 일차적인 목적이 함수의 이름에서 가르키고 있는 값을 반환하는 경우에는 함수를 사용하라. 그렇지 않다면 프로시저를 사용해라.

 

함수의 리턴값 설정 - 함수의 리턴값이 잘못된 값일 경우(난리난다)를 줄이는 다음의 지침을 기억하자

가능한 모든 리턴 경로를 검사하라

지역데이터에 대한 참조나 포인터를 리턴하지 말라.

 

  • 매크로 , inline 부분 생략

 

7장의 요점

  • 루틴을 작성하는 가장 중요한 이유는 프로그램의 지적인 관리성을 향상시키기 위한 것이다. 공간 절약은 중요하지 않다. 가독성, 신뢰성, 수정성의 향상이 더 좋은 이유이다.
  • 때떄로 별도의 루틴의 이득이 크지 않을수 있다
  • 루틴의 이름은 루틴의 품질이다. 정확한 이름의 정확한 루틴이 좋은 루틴이다
  • 함수는 그 주된 목적이 함수의 이름에 묘사된 특정한 값을 리턴하는 것일때에만 사용한다.
  • 신중한 프로그래머는 매크로를 주의 깊에 사용하여 최후의 수단으로 쓴다.

 

 

 

 

 

 

 

 

이 글은 스프링노트에서 작성되었습니다.

by 무위자연 2008. 1. 19. 17:06

타당하지 않은 입력으로부터 프로그램의 보호

외부에서 들어오는 모든 데이터의 값을 검사하라 : 문자열이라면 제한된 범위의 값이 맞는지 목적에 맞는 값인지 검사해야 한다

루틴의 모든 입력 매개변수를 검사하라.

잘못된 입력을 어떻게 처리할 것인지 결정하라

 

방어코드 작성의 가장 좋은 형태는 처음부터 오류를 입력하지 않는 것이다. 반복적인 설계, 코드를 작성하기 전에 의사코드 작성, 코드를 작성하기 전에 테스트 케이스 작성, 저수준 설계에 대한 정밀 검사 등은 모 두 결함의 입력을 예방하는 데 도움을 주는 활동들이다.

 

어설션(Assertion)

assertion은 프로그램이 실행될때 스스로를 검사할 수 있도록 개발 도중에 사용되는 코드이며 일반적으로 함수나 매크로로 사용된다.

코드에서 사정한 것들을 문서화하고 예상치 못한 조건을 찾아내기 위해서 assertion을 사용하라

  • 입력(출력) 매개변수의 값이 예상된 범위 안에 들어가는지
  • 파일이나 스트림이 루틴이 시작할때(끝날때) 열려있는지(닫혀있는지)
  • 파일이나 스트림이 루틴이 시작할때(끝날때) 시작(끝)이 있는지.
  • 파일이나 스트림이 읽기전용, 쓰기 전용, 읽기/쓰기 열려있는지
  • 입력만 가능한 변수의 값이 루틴에 의해서 변경되지 않는지
  • 포인터가 널이 아닌지
  • 루틴에 전달되는 배열이나 다른 컨테이너가 적어도 X개의 데이터 요소를 포함할수 있는지
  • 테이블이 실제 값을 포함할수 있도록 초기화되었는지
  • 컨테이너가 루틴이 시작할때(끝날때) 비어있는지(채워)
  • 최적화되어 있고 복잡한 루틴의 결과가 느리지만 분명하게 작성된 루틴과 일치하는지.

 

Assertion사용지침서

절대로 발생해서는 안되는 조건을 위해서 assertion을 써라

실행가능한 코드를 assertion내에 입력하지 않는다.

ex assertion을 위험하게 사용하고 있는 비주얼 베이직 예제

Debug.Assert(PerformAction());

ex. assertion을 안전하게 사용하고 있는 비주얼 베이직 예제

actionPerformed = PerformAction()

Debug.Assert(actionPerformed );

assertion이 실행되지 않아서 작업을 수행하는 코드가 컴파일 되지 않을수 있기때문에 수행하는 함수의 값을 따로 받은 다음에 그것을 assert해야 한다.

선행조건과 후행조건을 문서화하고 검증하기 위하여 assertion을 사용하라

 

오류처리기법

aseertion코드에서 절대로 발생해서는 안되는 오류를 처리하기 위하여 사용된다.

중립적인 값을 리턴해라. : 잘못된 데이터에 대한 가장 좋은 대응은 작업을 계속 수행하고 아무런 문제가 없다고 알려진 값을 리턴하는 것이다. 숙자계산에서는 0을 리턴할 것이고 문자연산에서는 빈 문자열을 리턴할 것이다.

다음에 오는 타당한 데이터로 대체하라

이전과 동일한 값을 리턴하라

가장 가까운 타당한 값으로 대체하라

경고메세지를 파일에 기록하라

오류코드를 리턴하라

오류처리 루틴이나 객체를 호출하라

오류가 발생한 곳에서 오류메세지를 출력하라

지역적으로 가장 잘 작동하는 방법으로 오류를 처리해라

종료하라

 

견고성과 정확성 - 소프트웨어 성격에 따라 달라진다. X레이촬영소프트웨어와 비디오게임이 같을순 없으니 신중하게 생각할 문제.

 

예외

예외는 코드가 오류나 예외적인 이벤트를 루틴을 호출한 코드에 전달할수 있는 특수한 방법이다. 만약에 어떤 루틴에 있는 코드가 어떻게 처리해야 하는지를 모르는 예외적인 상황이 발생하면, 예외를 던진다(throw)

무시되어서는 안되는 오류를 프로그램의 다른 부분에 알리기 위하여 예외를 사용하라.

정말로 예외적인 조건인 경우에만 예외를 던져라

책임을 전가하기 위해서 예외를 사용하지 마라

만약 생성자와 소멸자에서 예외를 잡을수 없다면 생성자와 소멸에서 예외를 던지지 마라

올바른 추상화 수준에서 오류를 던져라

예외를 야기한 모든 정보를 예외 메시지에 포함시켜라 - 비어 있는 catch블록을 피하라

라이브러리 코드가 던지는 예외를 파악하라

집중된 예외 보고자의 구축을 고려하라

 

예외 사용을 규격화해라

  • 프로젝트에서 던지는 모든 예외에 대한 기본 클래스로 사용할수 있도록, 프로젝트에 특화된 예외 클래스의 작성에 대해서 고려해본다. 기록, 오류 보고 등을 집중시키고 규격화할수 있다.
  • 어떤 코드가 지역적으로 오류를 처리하기 위해서 throw-catch문법을 사용할수 있는지에 대하 구체적인 상황을 정의한다
  • 어떤 코드가 지역적으로 처리되지 않는 예외를 던질수 있는지에 대한 구체적인 상황을 정의한ㄷ나
  • 집중된 예외 보고자가 사용될 것인지를 결정한다
  • 예외가 생성자와 소멸자에서 허용되는지 정의한다
  • 예외의 대안들을 고려해보자

 

오류에 의해서 발생하는 손해를 막기 위한 방책

특정한 인터페이스를 '안전한'지역의 경계로써 명시하는 것이다.안전한 지역의 경계를 지나는 데이터의 타당성을 검사하고 만약 데이터가 타당하지 않으면 현명하게 반응한다.

수술실(operating-room)기법이란 것이 있다. 데이터는 수술실에 들어가는 것이 허용되기 전에 살균된다. 수술실에 있는 것은 모두 안전하다고 여겨진다.

 

디버깅.

공격적인 프로그래밍을 사용하라

. 정상적으로 죽은 프로그램이 못쓰게 된 프로그램 보다 낫다.

  • 어설트가 프로그램을 중단하도록 한다. 프로그래머가 알려진 문제를 무시하기 위해서 enter키를 누르는 습관을  갖지 않도록 한다.
  • 할당된 모든 메모리를 완벽하게 채워서 메모리 할당 오류를 발견할수 있도록 한다
  • 파일 형식과 관련된 오류를 발견하기 위해서 할당된 파일이나 스트림을 완벽하게 채운다.
  • case 문의 default절이나 if문의 else 절이 심각한 문제를 일으키거나 (프로그램종료)간과하지 못하도록 한다
  • 객체를 삭제하기 전에 쓰레기 데이터로 채운다
  • 만약 여러분이 개발하고 있는 소프트웨어에 적합하다면, 오류 로그파일을 이메일로 보내도록 프로그램을 설정하여, 배포된 소프트웨어의 어떤 오류가 발생하고 있는지 확인할수 있다.

 

제품 코드 안에 남는 방어적인 프로그래밍 코드 정하기.

중요한 오류를 검사하는 코드는 남겨 놓아라

사소한 오류들을 검사하는 코드를 제거하라.

심각한 충돌을 야기하는 코드를 제거하라.

프로그램이 우아하게 충동하도록 돕는 코드를 남겨두라. - 만약 프로그램의 치명적인 오류를 감지하는 디버깅 코드를 포함하고 있다면, 프로그램이 우아하게 종료할수 있도록 그 코드를 남겨둔다.

기술적인 지원을 위해서 오류를 기록하라. - 제품코드에 디버깅 보조 도구를 남겨놓지만, 작동방식을 변경하지 않는 방법을 고려해본다. 만약 개발시에 프로그램을 중지시키는 aseertion을 코드에 포함시켰다면, 해당 assertion 루틴을 완전히 제거하기보다는 메시지를 파일에 기록할수 있도록 변경하는 것을 고려해볼수 있다.

여러분이 남겨둔 오류메시지가 친절한지 확인한다.

 

요점 정리

  • 제품코드는 쓰레기를 입력하면 쓰레기가 나온다 보다 정교한 방법으로 오류를 처리해야 한다.
  • 방어적인 프로그래밍 기법은 오류를 찾기 쉽고, 수정하기 쉽고, 제품 코드에 덜 손상을 입히도록 만든다
  • assertion은 오류를 초기에 발견하는데 특히, 큰 시스템, 신뢰성이 높은 시스템, 빠르게 코드가 변경되는 경우에 도움을 줄수 있다
  • 잘못도니 입력데이터를 처리하는 방법에 대한 결정은 오류 처리와 고수준 설계에 있어서 핵심적인 결정사항이다
  • 예외는 코드의 정상적인 흐름과 다른 차원에서 오류를 처리하는 방법을 제공한다.
  • 제품 시스템에 적용되는 제약 사항들이 반드시 개발 버전에 적용될 필요는 없다. 여러분은 그러한 사항들을 자신에게 유리하도록 사용할수 있으며, 오류를 빠르게 검출하는데 도움을 주는 코드를 개발 버전에 추가할수 있다.,

 

이 글은 스프링노트에서 작성되었습니다.

by 무위자연 2008. 1. 19. 17:06

의사코드 프로그래밍 프로세스(Pseudocode Programming Process)

 

전문가를 위한 의사코드

의사코드(pseudocode)라는 용어는 알고리즘이나 루틴, 클래스, 프로그램이 어떻게 작동할것인지를 기술하는 비형식적이고 자국어와 같은 표기법을 가르킨다.

의사코드를 효율적으로 사용하기 위한 지침들

  • 구체적으로 연산을 정확하게 기술하는 자국어와 같은 명령문을 사용한다
  • 대상으로 삼는 프로그래밍 언어의 문법적인 요소를 피한다.
  • 의사코드를 목적수준에서 작성한다.
  • 의사코드로부터 코드를 생성하는 것이 거의 자동으로 될수 있을 만큼 낮은 수준에서 의사코드를 작성한다. 코드를 쉽게 작성할수 있는 것처럼 보일때까지 좀더 다세하게 의사코드를 개선한다. 일단 의사코드가 작성되면, 의사코도 주위에 코드를 작성하고 의사코드를 프로그래밍 주석으로 변경한다.

    이런 원칙을 어기는 예제.

    ex. increment resource number by 1

      allocate a dlg struct using malloc

    if malloc() return NULL then return 1

    invokde OSrsrc_init to initialize a resource for the operating system

    *hRsrcPtr = resource number

    return 0

    이 예제가 말하는 것이 무엇일까? 어렵지만 말해보자면 *hRsrcPtr(C의 포인터)와 malloc(C 함수)와 같이 대상언너로 코드를 작성하기 위한 세부적인 사항들을 포함하고 있기때문에 잘못된 예이다. 이 의사코드 블록은 설계의 의미보다는 코드가 어떻게 작성될 것인지에 중점을 두고 있다.

    좋은 의사코드의 예.

    keep track of current number of resource in use

    If another resource is available

       allocate a dialog box structure

       if a dialog box structure could be allocated

               note that one more resource is in use

                initialize the resource

                 store the resource number at the location provided by caller

       endif

    endif

    return true if a new resource was created; else return false

    첫번째 보다 완전한 영어로 되어있어 좋고 대상언어의 문법적인 요소가 없어서 더욱 좋다.

  • 의사코드를 사용했을 때 기대할수 있는 이득.

    • 검토가 쉬워진다. 소스코드를 보지 않고 상세한 설계를 검토할수 있다
    • 반복적인 개선을 지원한다. 설계->의사코드, 의사코드->소스코드. 변환시킬때마다 개선할수 있는 여지가 있다
    • 변경을 쉽게 만든다.소스코드가 아니니까.초기에 오류를 잡을 가능성이 높다.
    • 의사코드는 주석을 작성하는 노력을 최소화한다. 전형적인 코드 작성 시나리오에서는 코드를 작성한 다음 주석을 추가한다.PPP에서는 의사코드 문이 주석문이 되기때문에 실제로 주석을 남겨놓는 것보다 주석을 제거하기 위해서 더 많은 작업을 한다.
    • 설계 문서화의 다른 형태보다도 유지 보수가 쉽다.

     

 

PPP를 사용한 구현 루틴.

루틴을 설계한다.

  • 사전에 필요한 조건들을 검사한다.
  • 루틴이 해결할 문제를 정의한다. - 루틴이 숨길 정보. 루틴에 대한 입력. 루틴으로부터의 출력. 루틴이 호출되기 전에 참이어야 하는 선행조건들, 루틴이 참임을 보장흔 후행조건들.
  • 루틴의 이름을 짓는다
  • 루틴을 어떻게 테스트할 것인지 결정한다
  • 표준 라이브러리에서 가능한 기능을 조사한다. - 라이브러리를 검색하는 습관은 중요하다. 품질과 시간. 생산성면에서 구현하고 하는 라이브러리가 이미 있을 경우 아주 뛰어나진다.
  • 오류처리에 대해서 생각한다
  • 효율성에 대해 생각한다.
  • 알고리즘과 데이터형을 조사한다
  • 의사코드를 작성한다. - 작성시에 일반적인 것에서 구체적인 것으로 작업을 진행한다. 각 루틴에서 가장 일반적인 요소를 무엇을 하는 루틴인지를 알리는 것이므로 무엇을 하는 루틴인지를 헤더에 주것다는 것에서 시작한다.
  • 데이터에 대해 생각한다.
  • 의사코드를 검사한다. - 작성한 의사코드가 설득력이 있는지 스스로가 검토해본다
  • 의사코드에서 몇가지 아이디어들을 시험해본후, 가장 좋은 방법을 유지한다.(iterative)

 

루틴의 코드를 작성한다.

  • 루틴의 선언부를 작성한다.
  • 의사코드를 고수준의 주석으로 변환한다.
  • 각각의 주석 아래에 코드를 채운다.
  • 코드가 더 나뉘어져야 하는지 검사한다. a. 만약 의사코드 한줄이 예상했던 것보다 많은 코드로 확장된다면 해당 코드를 별도의 루틴으로 나눌수 있다.

                                                          b. PPP를 귀납적으로 적용한다. 의사코드 한 줄 아래에 20줄이 넘는 코드를 작성하는 대신, 원래 있던 의사코드를 여러

                                                              줄의 의사코드로 분해하는 시간을 갖는다.

 

코드를 검사한다.

  • 루틴의 오류를 머릿속에서 검사한다.

    아마추어와 프로 프로그래머의 차이는 미신과 이해의차이점에서 온다. 미신은 코드에 대한 이해 대신 코드에 대한 느낌을 사용하는 것을 의미한다. 만약 컴파일러나 하드웨어가 오류를 만들었을 것이라고 자주 의심한다면, 여러분은 아직 미신의 영역에 있다. 한 조사에서 전체 오류 중에 5%정도가 ㅎ사드웨어나 컴파일러, OS의 오류라 한다. 이해의 영역에 있는 프로그래머는 언제나 자신이 작업한 내용을 가장 먼저 의심한다. 각 코드의 역할과 필요한 이유에 대해서 이해하도록 한다.

  • 루틴을 컴파일한다 - 루틴을 검사하고 나서 컴파일한다. 루틴을 좀 더 일찍 하면 문법오류를 더 빨리 찾을수 있다. 하지만 그렇게 하지 않는 이유는  새로운 코드를 컴파일할때 조급증이 생긴다. '컴파일을 한번 더 하면 제대로 동작해야해' '컴파일 한번 더' 같은 조급증은 오류를 유발할수 있는 변경을 만들고 길게 봤을때 더 많은 오류를 만들어낸다.- 좋은 컴파일에 대한 지침. 컴파일러의 경고수준은 가장 높게 설정./ 유효성검사기 사용/ 모든 오류메세지와 경고의 원인제거하고 컴파일러가 보여주는 메세지에 주의를 기울인다.
  • 코드를 디버거에서 한 단계씩 살펴본다.
  • 코드를 테스트한다.
  • 루틴에 있는 오류를 제거한다.

 

미해결된 부분을 정리한다.

  • 루틴의 인터페이스를 검사한다.
  • 일반적인 설계 품질을 검사한다.
  • 루틴의 변수를 검사한다
  • 루틴의 명령문과 논리적인 구조를 검사한다
  • 루틴의 배치를 검사한다
  • 루틴의 문서롸를 검사한다 - 주석으로 변환되는 의사코드가 여전히 정확한지 확인한다
  • 불필요한 주석을 제거한다

 

PPP의 대안

  • 테스트 우선 개발(test-first development) - 테스트케이스를 만들고 테스트코드를 작성하는 것.
  • 리팩토링 - 의미를 유지하는 일련의 변환 과정을 통해서 코드를 향상시키는 개발법
  • 계약에 의한 설계 - 각 루틴을 선행 조건과 후행조건을 갖는 것으로 간주하는 개발법.

 

요점정리 중

  • 훌륭한 의사코드를 작성하기 위해서는 이해가능한 언어를 사용하고, 단일 프로그래밍 언어에 특화된 기능을 피하고, 의도수준에서 작성해야 한다.
  • 의사코드 프로그래밍 프로세스는 상세 설계에 유용한 도구이며 코드 작성을 쉽게 만든다. 의사코드는 곧바로 주석으로 변환되며, 변환된 주석은 정확하고 유용하다.

이 글은 스프링노트에서 작성되었습니다.

by 무위자연 2008. 1. 19. 17:05

변수 선언을 쉽게 하는 방법

  • 암시적 변수선언을 지원하는 언어를 사용하더라도 사용하지 않는다.
  • 모든 변수를 선언한다.
  • 명명 규약을 사용한다.
  • 변수의 이름을 검사한다.

 

변수초기화에 대한 지침.

부적절한 초기화와 관련된 문제는 예상치 못한 초기값을 변수가 포함하고 있는 것에서 생긴다.

  • 변수에 값을 할당한 적이 없다
  • 변수에 있는 값이 더 이상 유효하지 않다
  • 변수의 일부에는 값을 할당하고 나머지 부분에는 값을 할당하지 않았다.
  • etc

 

초기화문제를 피하기 위한 지침들

  • 변수가 선언될때 초기화한다.
  • 변수가 처음 사용되는 곳에 근접한 위치에서 초기화한다. - VB처럼 선언할때 초기화하는 기능을 지원하지 않는 언어에서는 처음 사용하는 곳 근처에서 초기화해준다.근접성원리(관련 작업을 함께 유지한다) : 동일한 원리는 코드를 기술하는 주석을 코드와 가까운 위치에 두고, 루프의 근처와 가까운 위치에 두고, 직선형 코드에서 명령문을 그룹화하는 등 여러가지 영역들에 적용된다.
  • 이상적으로 , 각 변수가 처음 사용되는 곳에 근접한 위치에서 변수를 초기화하고 정의한다.
  • 가능하다면 final이나 const를 사용한다.- 변수를 자바에서의 final이나 c++에서의 const로 선언함으로써 변수가 초기화된 후에 다른 값으로 할당되는 것을 막을수 있다. final과 const키워드는 클래스 상수와 입력만 가능한 매개변수, 초기화된 후로 변경되지 않아야 하는 지역 변수를 정의하는데 유용하다.
  • 카운터와 누산기를 특히 주의한다. - j,k,k, sum 변수들은 종종 카운터와 누산기로 사용된다. 일반적인 오류는 카운터나 누산기를 다음 번 재사용 전에 초기화하는 것을 잊지 않는다.
  • 클래스 멤버데이터를 생성자에서 초기화한다
  • 다시 초기화를 해야 할 필요가 있는지 검사한다
  • 일단 명뎡된 상수를 초기화하고 실행코드로 변수를 초기화한다
  • 모든 변수들을 자동으로 초기화해주는 컴파일러 설정을 사용한다
  • 컴파일러의 경고 메시지를 활용한다
  • 입력매개변수의 타당성을 검사한다.
  • 부적절한 포인터를 검사하기 위해서 메모리 접근 도구를 사용한다
  • 프로그램 시작할때 작업 메모리를 초기화한다.

 

범위

변수에 대한 참조를 지역화하라 - 언제나 변수에 대한 참조들을 가까운 곳에 함께 두어 변수에 대한 참조를 지역화하는 것이 좋다. 변수에 대한 참조를 지역화한다는 개념은 별도의 설명이나 증명이 필요가 없지만, 형식적인 측정에 적합한 개념이다. 변수에 대한 참조를 가까이 유지하면, 여러분이 작성한 코드를 보는 사람이 한번에 한 섹션에 집중할수 있다. 만약 참조가 멀리 떨어져 있다면, 코드를 읽는 사람이 프로그램 주위를 이리저리 옮겨 다녀야 한다. 따라서 변수에 대한 참조를 가까이 유지하는 것은 프로그램의 가독성을 향상시키는데 좋은 방법이다.

 

변수의 수명을 가능한 한 짧게 유지한다. - 수명이란 변수가 처음 참조되는 곳에서 마지막으로 참조되는데까지를 말한다. 수명이 짧을수록 부정확한(부주의한) 변수변경을 막을수 있다. 또한, 코드에 대한 정확한 그림을 제공한다. 짧은 수명은 초기화 오류가 발생할 가능성을 줄여준다. 가독성도 높여준다.

 

범위를 최소화하기 위한 일반적인 지침

  • 루프에서 사용되는 변수는 루프를 포함하고 있는 루틴의 시작에서가 아니라 루프 바로 앞에서 초기화한다.
  • 변수를 사용하기 전까지 변수에 값을 할당하지 않는다.
  • 연관된 명령문들을 그룹화한다.
  • 연관된 명령문 그룹을 별도의 루틴으로 나눈다
  • 처음에는 가시도를 최대한 제한하고 필요한 경우에만 변수의 범위를 늘린다.

 

지속성

변수는 다음의 경우에 지속성을 가진다

  • 특정한 코드 블록이나 루틴에서만 살아남는 경우
  • 허용한 동안 살아남는 경우
  • 프로그램이 종료할때까지 살아남는 경우
  • 영원히 살아남는 경우 - 프로그램이 실행중이 아닐 때 DB에 저장한 값을 포함한 경우.
  •  

    문제는 변수가 실제 사용되는 것보다 지속성이 길 경우이다. 변수의 값이 정상적인 수명이 끝난 후에 사용하려면 잘못된 값이 들어있을 확률이 높다. 이런 문제를 피하기 위한 지침이 다음에 있다.

  • 중요한 변수가 적절한 값을 갖는지 검사하기 위해서 프로그램에 디버그 코드나 assertion을 사용한다
  • 변수를 사용하고 난 후에 변수에 "부적절한"값을 넣는다. 예를 들어 포인터라면 null을 설정해주는 것도 좋다
  • 데이터가 지속적이지 않다고 가정하는 코드를 작성한다.
  • 모든 데이터를 사용하기 바로 직전에 선언하고 초기화하는 습관을 들인다.

 

데이터 형과 제어 구조간의 관계

  • 순차적인 테이더는 프로그램의 순차적 명령문으로 변환한다.
  • 선택적 데이터는 프로그램의 if와 case명령문으로 변환한다.
  • 반복적 데이터는 프로그램에서 for, repeat, while loop구조로 변환한다

 

변수를 한 목적으로만 사용하는 방법

  • 각 변수를 한가지 목적만을 위해서 사용하라 - 다른 목적으로 변수를 사용하는 경우에 서로 관련이 없는 코드가 관련이 있어보이고 오류의 소지가 있다.
  • 숨겨진 의미를 갖는 변수를 피하라. 아래와 같이 변수를 남용하는 것을 hybrid coupling이라고 한다.

    • ex customerId변수는 고객번호를 표현한다. 하지만 이 값이 500,000보다 크면 연체된 계정의 수를 구하기 위해서 500,000을 뺀다
    • bytesWritten변수는 출력파일에 쓰여진 바이트 수이다. 하지만 이 값이 음수이면 출력 파일에 사용된 디스크 드라이브 수를 가르킨다.
  • 선언된 모든 변수가 사용되는지 확인하라. - 참조되지 않는 변수는 높은 오류 발생률과 상관관계가 있다.

 

요점정리

  • 데이턴 선언은 오류가 발생하기 쉽다. 따라서 예상치 못한 초기 값으로 인해서 발생하는 문제를 피하기 위하여 초기화기법들을 사용한다
  • 각 변수의 범위를 최소화하고, 변수에 대한 참조를 가까이 유지한다. 루틴이나 클래스로 제한하고 전역데이터를 피한다
  • 같은 변수를 사용하는 명령문은 가능한한 가깝게 유지한다
  • 각 변수를 한가지 목적을 위해서만 사용한다

이 글은 스프링노트에서 작성되었습니다.

by 무위자연 2008. 1. 19. 17:05

좋은 이름에 대한 고려사항

변수의 이름에 따라 변수의 좋고 나쁨이 크게 좌우된다.

나쁜 변수명의 예제

> x = x - xx;

   xxx = fido + SalesTax(fido);

   x = x + LateFee(x1, x) + xxx;

   x = x + Interest(x1, x);

  //이 코드는 미결재 잔액과 새로 구입한 물건들을 토대로 고객이 지불해야 하는 총 금액을 계산하는 코드..라지만 각 변수의 의미를 알기 힘들다

좋게 수정하면

balance = balance - lastsPayment;

mothlyTotal = newPurchases + SalesTax(newPurchases);

balance = balance + LateFee (customerID, balance) + monthlyTotal;

balance = balance + Interest(customerID, balance);

 

명명(naming)시 가장 중요한 고려사항

변수이름이  변수가 표현하고 있는 것을 완벽하고 정확하게 설명해야 한다는 점이다. 종종 서술문 자체가 가장 좋은 변수 이름인 경우가 있다. 미국 올림픽대표팀에 있는 선수의 수를 표현하는 변수이름은 numberofPeopleOntheUsOlympicTeam일 것이다. 운동장 좌석수를 표현하는 이름은 numberofSeatsInTheStadium일 것이다. 현재의 이자율을 포함하는 변수는 r이나 x보다 rate나 interestRate가 더 좋은 이름이다.

이러한 이름에는 두가지 특징이 있음을 주목하자. 첫째, 판독하기 쉽다. 둘째는 몇몇 이름은 너무 길어서 실용적이지 않다.

이름은 가능한한 구체적이어야 한다. x, temp, i와 같이 한가지 이상의 목적으로 사용되기 쉬운 막연한 이름들은 많은 정보를 제공하지 않기때문에 나쁜이름이 된다.

 

최적의 이름 길이는 10~16자 사이가 적당하다고들 한다.

 

범위가 변수 명에 미치는 효과

전역공간(namespace)에 있는 이름들에는 한정자(qualifier)를 사용하라

 

변수이름에서의 계산값 한정자 - 많은 프로그램들은 계산된 값(총계, 평균, 최대값 등)을 보관하는 변수들을 갖는다. 만약 Total, Sum, Average, Max, Min, Record, String, Pointer와 같은 한정자로 변수의 이름을 수정한다면, 이름의 끝에 수정자를 입력한다. 이런 습관의 장점은 첫째, 변수 이름에서 가장 중요한 부분, 즉 변수의 의미를 가장 잘 전달하는 부분은 앞부분이기때문에 가장 눈에 띄고 가장 먼저 읽혀야 한다.  둘째, 이러한 규약을 마련함으로써 totalRevenue와 revenueTotal을 같은 프로그램에서 사용하게 되는 혼란을 피하게 된다. 셋째, revenueTotal, expenseTotal, revenueAverage와 같은 이름들은 정렬된 느낌을 주지 않는다. 마지막으로 일관성은 가독성을 향상시키고 유지보수를 쉽게 한다.

 

변수이름의 일반적인 반의어

  • begin/end
  • first / last
  • locked / unlocked
  • min / max
  • next / previous
  • old / new
  • opened / closed
  • visible / invisible
  • source / target
  • source / destination
  • up / down

 

데이터의 특정 타입에 대한 명명

루프인텍스의 명명

우리는 루프에서 i,j,k같은 이름을 관습적으로 쓴다.

ex 간단한 루프 변수 이름에 대한 자바 예제

for( i = firstItem; i < lastItem; i++)

data[ i ] = 0;

만약 변수가 루프 외부에서 사용되어야 한다면, 반드시 i나 j,k보다는 좀 더 의미 있는 이름을 제공해야 한다. 예를 들어, 파일로부터 레코드를 읽어들이고 얼마나 많은 레코드를 읽었는지 기억해야 한다면, recordCount와 같은 이름이 적절할 것이다.

ex 실명적인 루프변수 이름에 대한 자바 예제

recordCOunt = 0;

while( moreScores())

{

Score[ recordCount ] = GetNextScore();

recordCount ++;

}

//recordCount를 사용하는 코드

만약 루프가 길어진다면, i가 무엇을 나타내는지 잊기 쉽기때문에, 루프의 인덱스에 좀 더 의미 있는 이름을 제공하는 것이 좋을 것이다. 코드는 자주 변경되고 확장되고 다른 프로그램에 복사되기 때문에, 많은 숙련된 프로그래머는 i와 같은 이름들을 피한다. 루프가 길어지는 한가지 이유는 루프가 중첩되기 때문이다. 만약 여러개의 중첩된 루프가 있다면 가독성을 향상시키기 위해서 좀 더 긴 이름으로 루프 변수들을 작성한다

ex 중첩된 루프에서 좋은 루프 이름을 갖는 자바 예제

for ( teamIndex = 0; teamIndex < teamCount; teamIndex++)

{

for( eventIndex = 0; eventIndex < eventCount[ teamIndex ] ; eventIndex++)

{

score[teamIndex][eventIndex] = 0;

}

}

j,i보다는 teamIndex, eventIndex가 많은 정보를 제공한다

 

상태변수의 명명

상태변수에 대해서 flag보다 더 나은 이름을 생각한다.

ex 나쁜 이름을 갖는 플래그에 대한 예제

if ( flag ) ...

if( statusFlag & 0x0F) ...

if( printFlag == 16) ...

if(computeFlag == 0) ...

flag = 0x1;

statusFlag = 0x80;

printFlag = 16;

computeFlag = 0;

 

statusFlag = 0x80와 같은 명령문은 여러분이 코드를 작성하거나 관련문서를 보기전까지는 무엇을 의미하는지 알수 없다

ex상태변수를 잘 사용한 예제

if(dataReady) ...

if(characterType & PRINTABLE_CHAR) ...

if(reportType == ReportType_Annual) ...

if(recalcNeeded == True) ...

dataReady = true;

characterType = CONTROL_CHARACTER;

reportType = ReportType_Annual;

recalcNeeded = false;

이것이 좋은 명명 법이다.

 

임시 변수의 명명

임시변수는 계산의 중간 결과를 보관하기 위한 임시 저장소로 사용되고 보고 수잔으로 사용되는 값을 보관하기 위해 사용된다. 일반적으로 temp, x 또는 그 밖의 모호하고 설명적이지 않은 이름으로 명명된다.

임시변수를 조심해라. 변수의 값을 일시적으로 유지해야 할 필요가 종종있다.

 

ex 정보가 없는 임시 변수의 이름에 대한 예제

// 2차 방정식근을 구하는 코드이고 값은 양수를 가정한다

temp = sqrt(b^2 - 4 * a * c);

root[0] = (-b + temp) / ( 2 * a);

root[1] = ( -b - temp) / (2 * a);

temp가 어떤 값을 가지고 있는지 아무 정보가 없다.

 ex 임시 변수의 이름을 실질적인 변수로 대체한 예제

dicriminant = sqrt(b^2 - 4 * a * c);

root[0] = (-b + dicriminant ) / ( 2 * a);

root[1] = (-b - dicriminant ) / ( 2 * a);

 

boolean 변수의 명명

  • 전형적인 불린 변수의 이름을 기억한다. 다음은 유용하게 사용되는 불린 변수의 이름들이다

    • done - 무언가의 수행이 완료됨을 의미
    • error - 오류가 발생했음
    • found - 값이 발견되었음. 값이 발견되었으면 참 아니면 거짓.
    • soccess | ok - 연산의 성공을 의미.실패하면 거짓
  • 참이나 거짓의 의미를 함축하는 불린 변수의 이름을 사용한다. - 위와 같이 참 거짓이 명확한 명명은 좋지만 status / sourceFile같은 변수명은 참 거짓이 불분명하기때문에 좋지 않다. 어떤 프로그래머는 불린변수이름 앞에 Is를 붙이기 좋아한다. isdone? isError? isFound? 이 질문에 대한 참, 거짓의 대답을 제공한다. 하지만 isStatus?는 말이 안되는 이름인 것이다. 이 접근의 단점은 간단한 논리적인 표현식의 가독성이 떨어진다는 것이다. if(isFound)는 if(found)보다 가독성이 떨어진다.
  • 긍정적인 불린 변수이름을 사용한다 - notFound, notdone, notSuccessful보다는 found/done/processingComplet가 훨씬 좋다.

 

열거 형의 명명

열거형을 사용할때, Color_, Planet_, Month_와 같은 접두사를 사용하여 해당타입의 멤버들이 모두 동일한 그룹에 속한다는 것이 분명하다는 것을 보장할수 있다.

 

상수의 명명

상수를 명명할때 상수가 가르키는 숫자보다는 상수가 표현하는 추상적인 대상으로 명명하도록 한다. FIVE는 나쁜 이름이다. CYCLES_NEEDED는 좋은 이름이다.FIVER = 6.0도 웃긴 이름이다. 마찬가지로 BAKERS_DOZEN은 잘못된 상수이름이며,DONUTS_MAX는 좋은 이름이다.

 

명명 규약의 효과

규약의 장점

  • 더 많은 것을 당연하게 받아들일수 있다
  • 다른 프로젝트에서 활용할수 있다. 유사한 이름은 무엇을 해야 하는지 익숙하지 않은 프로젝트에서 도움을 준다
  • 새로운 프로젝트에서 좀 더 빠르게 코드를 배우는데 도움을 준다. 프로그래머마다 다르게 보일수 있는 코드를 일관성있게 만들어 준다
  • 이름이 늘어나는 것을 줄여준다. 예를 들어 총점을 pointTotal이나 totalPoints로 부를수 있다. 이것은 코드 작성시에는 괜찮지만 가독시 혼란스러울수 있다
  • 언어의 약점을 보완할수 있다
  • 관련된 항목들간의 관계를 강조한다. 객체데이터를 지원하는 언어를 사용한다면 컴파일러가 자동으로 처리하지만 그렇지 않을 경우 명명 규약으로 보완할수 있다. address, phone, name과 같은 이름들의 변수가 있고 직원(employee)와 관련된 변수들이라면 employeeAddress, employeePhone, employeeName 이라 지어보자. 관련성이 눈이 보일 것이다.

 

언제 명명 규약이 필요한가? - 솔직히 항상 필요한 거 같기는 하다.

  1. 한 프로젝트에서 여러 명의 프로그래머가 작업할때
  2. 수정이나 유지보수때문에 프로그램을 다른 프로그래머에게 넘겨주어야 할때(거의 항상)
  3. 조직 내에 있는 다른 프로그래머가 여러분이 작성한 프로그램을 검토할때
  4. 프로그램이 너무 커서 한번에 기억할수 없기때문에 반드시 부분적으로 제쳐두어야 할때
  5. 프로젝트에서 자주 쓰이는 특이한 용어들이 많아서 코드 작성시 사용할 표준 용어나 약어가 필요할때

 

비형식적인 명명 규약

언어에 독립적인 규약을 위한 지침

  • 변수 이름과 루틴 이름을 구별한다 - 변수이름은 소문자로 시작하고 루틴이름은 대문자로 시작하는 경우가 많다
  • 클래스와 객체를 구별한다

    1. 첫번째 문자를 대문자로 작성하여 타입과 변수를 구분함

      ex. LongerWidget longerWidget;

    2. 모든 문자를 대문자로 작성하여 타입과 변수를 구분함

      ex. WIDGET widget; LONGERWIDGET longerWidget;

    3. 타입에 대해서 "t_" 접두사를 작성하여 타입과 변수를 구분함

      ex. t_Widget Widget; t_LongerWidget LongerWidget;

    4. 변수에 대해서 "a"접두사를 작성하여 타입과 변수를 구분함

      ex. Wdiget aWidget; LongerWdiget aLongerWidget;

    5. 보다 구체적인 변수의 이름을 사용하여 타입과 변수를 구분함

      ex. Widget employeeWidget; LongerWidget fullEmployeeWidget;

      1~5는 각각 tradeoff가 있다. 각자 사용하는 언어의 특성에 맞추어 알아서 사용하면 된다. 예를 들어 1번째 경우 VB에서는 widget과 Widget을 같은 토큰으로 간주해서 사용할수 없다.

전역 변수를 식별한다 - 예를 들어 "g_"같은 접두어를 붙여서 식별한다

멤버변수를 식별한다 - 예를 들어 "m_"같은 접두어

형 선언을 식별한다 - 접두어 "t_"

명명된 상수를 식별한다

열거형의 요소를 식별한다

입력만 하는 매개변수를 지정할수 없는 언어에서는 이를 식별한다

가독성을 강화시키기 위해서 이름을 형식화한다- 가독성을 높이기 위해서 대문자를 사용하는 방법과 공백문자를 사용하는 방법이 있다. GYMNASTICSPOINTTOTAL은 gymnasticsPoi0ntTotal이나 gymastics_point_total보다 가독성이 떨어진다. 이런 기법은 섞어서 사용하지는 말자.

 

특정언어에 대한 지침. - 너무 구체적임으로 책 참고 - p395~p397.게다가 현재 난 AS를 사용한다

 

표준화된 접두사들

표준화된 접두사는 사용자 정의 타입의 축약형(User-Defined Type, UDT)와 의미적인 접두사로 나눈다.

UDT - 특정 프로그램을 위해서 작성한 짧은 코드로 기술한 후, 그 프로그램에서 사용하기 위하여 표준화한다

아래 표는 워드 프로세서를 위한 프로그램에 사용할수 있는 간단한 UDT목록이다.

UDT 축약형 의미
ch 문자에서(c++에서의 문자가 아니라 워드 프로세서 프로그램 문서에 있는 문자를 표현하기 위한 데이터형
doc 문서.document
pa paragraph
scr 화면 영역(screen region)
sel 선택(selection)
wn 윈도우(window)

의미적 접두사 - 의미적 접두사는 UDT보다 한 단계 더 나아가서 변수나 객체가 어떻게 사용되는지를 설명한다

의미적 접두사 의미
c 카운트(count), 레코드나 문자 등의 수에서 사용된다
first 배열에서 다루어져야 하는 첫번째(first)요소이다. first는 min과 유사하지만 배열 자체보다는 현재의 연산과 관계가 있다
g 전역변수
i 배열에 대한 인덱스
last 배열에서 다루어져야 하는 마지막(last)요소이다. last는 first의 반대
lim 배열에서 다루어져야 하는 요소의 상한ㄱ밧
m 클래스 수준의 변수
max 배열이나 다른 종류의 리스트에서 절대적인 마지막 요소. max는 배열에 대한 연산보다는 배열자체를 가르킨다
min 배열이나 다른 종류의 리스트에서 절대적인 첫번째 요소
p 포인터

표준화된 접두사의 장점

모호해지기 쉬운 명명 영역을 정교하게 만든다. min과 first, last, max간의 정확한 구분은 특히 도움이 된다.

간결한 이름을 만든다. 예를 들어 단락수를 나타내기 위해 totalParagraphs 대신 단락수로 cpa를 사용할수 있고 indexParagraphs나 paragraphsIndex대신에 단락배열에 대한 인덱스를 구별하기 위해서 ipa를 사용할수 있다.

표준화된 접두사를 이용하면 컴파일러가 검사할수 없는 추상 데이터 형을 사용하고 있을 때 타입을 정확하게 검사할수 있다. paReformat = docReformat은 pa와 doc가 서로 다른 UDT이기때문에 아마 틀렸을 것이다.

표준화된 접두사의 가장 큰 위험은 프로그래머가 접두사에 이어서 변수에 의미 있는 이름을 제공하려고 하지 않는 다는 점이다.

 

읽기 쉬운 짧은 이름

일반적인 축약 지침

  • 표준 축약어를 사용한다(사전에 나와 있는 널리 사용되는 용어)
  • 불필요한 모음을 제거한다(ex. computer > cmptr, screen > scrn, apple > appl, integer > intgr) -> 한국인이라 그런지 공감 안됨. 더 모르겠음
  • 관사(and, or, the )를 제거한다
  • 각 단어의 첫번째 문자나 처음의 몇 문자를 사용한다
  • ...

축약어에 대한 의견.

-->별공감은 가지 않지만 한가지는 기억해둘만 하다. 주요 축약어에 대한 설명을 주석으로 넣어둔다.

ex. xPos x값의 위치 . 이런 식으로.

 

피해야 할 종류의 이름

  • 오해의 소지가 있는 이름이나 축약어를 피한다
  • 유사한 의미를 갖는 이름을 피한다
  • 의미는 다르지만 유사한 이름의 변수를 피한다
  • "개"와 "게"처럼 비슷하게 들리는 이름을 피한다 > 동의이의어
  • 이름에서 숫자를 피한다 - 예를 들어 file1과 file2 또는 total1과 total2는 피한다.
  • 이름에 철자가 틀린 경우가 없도록 한다
  • 일반적으로 틀리기 쉬운 단어는 피하도록 한다- 철자가 미국인도 헷갈리는 것들이 참 많구나
  • 대문자 만드로 변수의 이름을 차별화하지 않는다.
  • 여러개 언어의 사용을 피한다
  • 표준형, 변수, 루틴의 이름을 피한다 - 일밙적으로 예약어.
  • 변수가 표현하는 것과 전혀 관련이 없는 이름을 사용하지 않는다
  • 읽기 어려운 문자를 포함하는 이름을 피한다

 

 

요점정리

  • 좋은 변수 이름은 프로그램의 가독성에 있어서 핵심적인 요소이다
  • 이름은 가능한한 구체적이어야 한다
  • 명명규약은 지역, 클래스, 전역 데이터를 구분한다. 그리고 형이름, 명명된 상수, 열거형, 변수를 구분한다
  • 축약어는 현대적인 프로그래밍 언어에서는 거의 필요하지 않다.
  • 코드는 작성되는 것보다 훨씬 많이 읽혀진다. 여러분이 선택한 이름이 작성 시간 편의성보다 읽기 시간 편의성에 유리하도록 한다

이 글은 스프링노트에서 작성되었습니다.

by 무위자연 2008. 1. 19. 16:58

일반적인 숫자

매직넘버는 피한다 : 아무 의미 없는 혹은 의미를 알기 힘든 숫자를 사용하는 것을 지양한다

ex for i= 0 to 99 do... 이것보다는 for i = 0 to MAX_ENTRIES - 1 do... 이 코드의 가독성도 높이고 숫자가 의미하는 바가 확실하다.

하지만 필요하다면 0과 1은 그냥 사용한다

0으로 나눔오류를 미연에 방지한다.

형변환을 명확하게 수행한다. -> 잘못된 형변환은 값의 손실을 가져온다.

서로 다른 형을 비교하지 않는다. -> 이것 역시 잘못된 결과값이나 값의 손실을 가져올수 있다.

컴파일러의 경고에 주의를 기울여라

 

정수

정수나눗셈을 검사한다 - 7/10 의 결과는 반드시 0.7이 아닐수 있고 언어마다 다를수 있다. 가장 좋은 방법은 (10 * 7) / 10으로 바꾸는 것이다.

정수 오버플로우를 검사한다

중간 결과에 오버플로우를 검사한다

 

부동소수점

부동소수점에서 가장 중요하게 고려해야 할 사항은 많은 분수값이 디지털 컴퓨터의 0과 1을 사용하여 정확하게 표현될수 없다는 점이다.

서로 크기가 매우 다른 수를 더하거나 빼지 않는다

동치비교를 피한다

라운딩 오류를 예측한다 - 더 큰 정밀도를 갖는 변수 형으로 변경한다, 이진화 십진 표기법 변수로 변경한다, 부동소수점을 정수형 변수로 변경한다

특정 데이터형을 지원하는 언어와 라이브러리가 있는지 확인한다

 

문자와 문자열

매직 문자와 문자열을 사용하지 않는다

하나 모자름(off-by-one)오류를 주의한다 - 서브 문자열은 배열처럼 인데스로 나타낼수 있기때문에, 문자열의 범위를 지나서 읽거나 쓰는 하나 모자름 오류를 주의한다

사용하고 있는 언어의 환경에서 유니코드를 어떻게 지원하는지 알아보도록 한다

국제화/ 지역화 전략을 초기에 결정한다 - 나한테 다국어버전 이슈로 다가옴!!!!

문자열 타입간에 일관된 변환 전략을 결정한다

 

C에서의 문자열

문자열 포인터와 문자 배열간의 차이점을 이해하도록 한다

 C방식의 문자열을 CONSTANT+1의 길이를 갖도록 선언한다

ex. char name[NAME_LENGTH + 1] = {0}; // 길이가 NAME_LENGTH인 문자열

끝이 없는 문자열을 피하기 위해서 문자열을 널(null)로 초기화한다

ex. char EventName[MAX_NAME_LENGTH + 1] =  {0};

C언어에서 포인터 대신 문자 배열을 사용한다 - 메모리 제약만 없다면 모든 문자열 변수들을 문자 배열로 선언한다

끝이 없는 문자열을 피하기 위해서 strcpy()대신 strncpy()를 사용한다

 

 Boolean 변수

프로그램을 문서화하기 위해서 불린 변수들을 사용한다. - 불린 표현식을 테스트 하는 대신, 표현식의 의미가 보다 명확하도록 변수에 할당한후 테스트를 수행한다

ex. 불린 테스트의 목적이 명확하지 않은 자바 예제

if((elementIndex < 0) || ( MAX_ELEMENTS < elementIndex) || (elementIndex == lastElementIndex)){ ... }

ex. 불린 테스트의 목적이 명확한 자바 예제

finished = (elementIndex < 0) || ( MAX_ELEMENTS < elementIndex);

repeatedEntry = (elementIndex == lastElementIndex);

if( finished || repeatedEntry) { ... }

복잡한 테스트를 단순하게 하기 위하여 불린 변수를 사용한다 - 여러개의 조건을 나누어 불린 변수에 넣고 그 변수로 if문의 조건을 만든다. 위의 예처럼.

필요하다면, 고유한 불린 타입을 만든다.

 

열거형

가독성을 향상시키기 위해서 열거형을 사용하라

신뢰성을 위해서 열거형을 사용한다

수정 용이성을 위해서 열거형을 사용한다

불린 변수에 대한 대안으로 열거형을 사용한다.

타장하지 않은 값을 검사하라 - if나 case문에서 열거형을 사용할때, 타당하지 않은 값을 검사한다. 타장하지 않은 값을 잡기 위해서 case문에서는 else문을 사용한다

ex. 열거형에서 타당하지 않은 값을 검사하는 풀륭한 VB예제

Select Case screenColor

case Color_Red //...

case Color_Blue //...

case Color_Green //...

case Else -> 여기에서 타장하지 않은 값을 테스트하고 있다.

        displayInternalError()

반복문의 범위를 지정하기 위해서 열거의 처음과 마지막 엔트리를 정의한다

열거형의 첫번째 항목을 타당하지 않은 값으로 남겨둔다 - 많은 컴파일러들은 열거 형의 첫번째 요소를 0으로 할당한다. 0으로 매핑된 해당요소를 타당하지 않은 값으로 선언하면 초기화되지 않은 변수들은 0일 확률이 높기때문에, 그러한 변수들을 잡는데 도움을 준다.

프로젝트 코드작성표준에서 처음과 마지막 요소가 어떻게 사용될 것인지를 정확하게 정의한 후, 일관성 있게 사용한다

열거의 요소에 명시적인 값을 할당할때 발생할수 있는 위험요소들을 주의한다

 

만약 언어가 열거형을 지원하지 않는 다면? 전역변수나 클래스로 열거형을 흉내낼수 있다.

ex 열거형을 흉내내는 자바예제

class Country

{

private Country()

{

public static final Country China = new Country();

public static final Country England = new Country();

public static final Country Korea = new Country();

//..

}

}

 

명명된 상수

데이터선언에서 명명된 상수를 사용해라 - 가독성과 유지보수에 도움을 준다

"확실한" 리터럴이라도 리터럴은 피한다.

적절하게 지역변수나 클래스를 사용하여 명명된 상수를 흉내낸다

명명된 상수를 일관성 있게 사용해라

 

배열

배열은 모든 항목이 동일한 형이고 배열 인덱스를 사용하여 직접적으로 접근 가능한 항목을 포함한다

배열의 모든 인덱스가 배열의 경계내에 있는지 확인한다

배열 대신 컨테이너의 사용을 고려하거나 배열을 순차적인 구조체로 생각하라

배열의 마지막 위치를 확인해라

다차원 배열에서는 첨자(subscript)가 정확한 순서대로 사용되고 있는지를 확인하도록 한다

인덱스가 혼선(cross-talk)되지 않도록 주의한다 - 중첩된 루프경우 Array(i)를 Array(j)이라고 작성하기 쉽다. 조심하자.

C에서는 배열을 다루기 위해서 ARRAY_LENGTH()매크로를 사용한다.

ex #defineARRAY_LENGTH (x) (sizeof(x) / sizeof(x[0]))

 

새로운 형 만들기

새로운형을 만들기 위한 지침들

기능 지향적인 이름으로 형을 생성한다 - 새로운 형이 표현하고 있는 실제 세계문제의 일부분을 가르키는 형이름을 사용한다.

미리 정의된 형을 피한다

미리 정의된 형을 재정의하지 않는다 - 표준형의 선언을 변경하면 혼란스럽다

이식성을 위해서 대체형을 정의하라

tyepdef를 사용하는 대신 클래스 생성을 고려해본다.`

이 글은 스프링노트에서 작성되었습니다.

by 무위자연 2008. 1. 19. 16:58

구조체

서로 다른 형들의 집합으로 구성된 데이터를 의미한다. 배열은 이 중 특이한 경우인 것이다. 보통은 구조체보다는 클래스를 선호한다

구조체를 사용하는 예

데이터 관계를 이해하기 쉽게 하기 위해서 구조체를 사용한다

ex 오해의 소지가 있고 구조화되지 않은 변수를 사용한 VB 예제

name = inputname;

address = inputAddress;

phone = inputphone;

title = inputtile ;//...

ex 보다 많은 정보를 제공하고 구조화된 변수를 사용하는 VB 에제

employee.name = inputname;

employee.address = inputAddress;

employee.phone = inputphone;

employee.title = inputtile; //...

 

데이터 블록에 대한 작업은 단순화하기 위해서 구조체를 사용해라

매개변수 목록을 단순화시키기 위해서 구조체를 사용해라 - 구조체를 사용하여 루틴의 매개변수를 단순화할수 있다. 필요한 각각의 요소들을 개별적으로 전달하는 대신 연관된 요소들을 구조체로 묶은 다음, 구조체로 전체 내용을 전

달할수 있다.

ex 구조체를 사용하지 않고 서투른 방법으로 루틴을 호출하는 VB예제

HardWayRoutine { name, address, phone, ssn, gender, salary }

ex 구조체를 사용하여 세련된 방법으로 루틴을 호출하는 VB 예제

EasyWayRoutone { employee }

만약 numWithholdings를 추가하고 싶다면 HardWayRoutine 에 대한 호출을 모두 변경해주어야 한다. 하지만 employee 에 추가한다면 EasyWayRoutone 는 매개변수를 신경쓸 필요가 없다

유지보수를 줄이기 위해서 구조체를 사용해라 - 구조체를 사용하면 연관된 데이터를 그룹으로 묶기때문에 구조체를 변경할때 프로그램을 변경하는 경우가 적다.

 

포인터

포인터 사용은 현대적인 프로그래밍에서 오류를 유발할 가능성이 가장 높은 분야 중 하나이다. 그리고 자바와 C#, VB와 같은 현대적인 어너는 포인터 데이터형을 제공하지 않는다. 하지만 포인터를 잘 이해하면 프로그래밍 언어의

동작방식을 이해하는데 도움이 되고 여러가지 방어적인 프로그래밍 방법들에도 많은 도움이 된다.

포인터를 이해하는 패러다임

메모리상에서의 위치 - 메모리상에서의 위치는 주소이며 종종 16진수 표기법으로 표현된다. 포인터 자체는 이 주소만 포함한다. 포인터가 가르키고 있는 데이터를 사용하기 위해서는 해당주소로 가서 메모리의 내용을 해석해야 한다

메모리의 내용을 해석하는 방법 - 해당 포인터의 기본형에 의해서 제공된다. 정수를 가르키면 정수로, 문자열을 가르키면 문자열로 해석할수 있다.

포인터 사용의 팁

포인터 오류는 일반적으로 포인터가 절대로 가리켜서는 안되는 어딘가를 가르키기때문에 발생한다. 잘못된 어떤 값을 포인터 변수에 할당할때 써서는 안되는 메모리 영역에 데이터를 쓰게 된다. 이를 메모리 충돌(memory corruption) 이라 한다. 포인터의 성공적인 사용을 위해서 첫째, 처음부터 포인터 오류를 만들지 않는다. 둘재, 포인터 오류가 코드에 작성된 후 가능한한 빨리 발견되도록 한다.

  • 포인터 연산을 루틴이나 클래스로 고립시킨다.
  • 포인터를 선언과 동시에 정의해라.

        ex 잘못된 포인터 초기화 C++예

        Employee *employeePt;

        // 수 많은 코드

        employeePtr = new Employee();

       ex 포인터를 훌륭하게 초기화한 C++예

       //수 많은 코드

      Employee *employeePtr = new Employee();

  • 포인터를 할당된 곳과 같은 영역 내에서 삭제한다
  • 포인터를 사용하기 전에 검사한다
  • 포인터가 참조하는 변수를 사용하기 전에 검사한다
  • 손상된 메모리를 검사하기 위해서 도그태그(dog-tag)필드를 사용한다.
  • 명시적으로 중복하여 추가한다
  • 여분의 포인터 변수들을 사용한다ㅏ - 절대로 포인터 변수들을 아끼지 않는다. 변수는 하나 이상의 목적으로 사용되어서는 안된다.
  • 복잡한 포인터 표현식을 단순화해라
  • 그림을 그린다. - 포인터를 코드로 설명하면 혼란스러울수 있다. 일반적으로 그림을 그리는 것이 도움이 된다.
  • 연결 리스트에 있는 포인터들을 올바른 순서로 삭제한다
  • 임시메모리를 할당한다 - 만약 프로그램이 동적 메모리를 사용항다면, 갑자기 메모리 부족하게 되어 RAM공간에 있는 데이터를 잃게 되는 문제를 피해야 한다. 이러한 문제를 피하는 방법중에 하나가 임시메모리를 할당하는 것이다. 작업을 저장하기 위해서 얼마나 많은 메모리가 필요한지 결정한 다음, 작업을 정리한후 우아하게 프로그램을 종료시킨다. 즉 프로그램이 시작할때 메모리를 할당하고 남겨둔다. 그러면 프로그램에서 메모리가 부족하게 될 경우 임시 메모리를 해제한후 작업을 정리하고 종료한다
  • 쓰레기를 확실하게 제거해라.
  • 메모리를 삭제하거나 해제한 다음 포인터를 null로 설정한다.
  • 변수를 삭제하기 전에 잘못된 포인터를 검사한다
  • 포인터할당을 추적한다
  • 포인터문제를 피하기 위한 전략에 집중하기 위하여 커버(cover)루틴을 작성한다

 

C++ 포인터 포인터

  • 포인터와 참조의 차이점을 이해하라 - 포인터(*)와 참조(&) 모두 객체를 간접적으로 참조한다. 초보자입장에선 object->field와 object.field로 참조하는 외형 상의 차이가 있다. 하지만 가장 중요한 차이점은 참조가 항상 객체를 참조하는 반면에 퐁린터는 널을 가르킬수 있고 참조는 참조하는 대상이 초기화 된 후로 변경될수 없다
  • 참조로 전달 매개변수에 포인터를 사용하고 값으로 전달매개변수에 const참조를 사용해라 - 수정가능한 객체에서는 멤버에 대한 참조를  object->member표기법으로 사용할 것이며, 수정 가능하지 않은 객체에는 멤버에 대한 참조를 object.member표기법으로 사용할 것이다.
  • auto_ptrs를 사용해라 - auto_ptrs를 사용하는 습관을 들이도록 하자. auto_ptrs이 범위를 벗어날때 자동으로 메모리를 삭제함으로써, 일반적인 포인터와 관련된 많은 메모리 누수문제를 피할수 있다.
  • 스마트포인터(smart pointer)사용해라. - 스마트 포인터는 일반 포인터와 비슷하게 작동하지만 자원관리, 복사작업, 할당 연산, 객체 생성과 소멸등의 기능을 제공한다.

 

C - 포인터 포인터

  • 기본 형 대신 명시적인 포인터 형을 사용해라 ex NodePtr = (NODE_PTR) calloc (1, sizewof(NODE));
  • 형 변환을 피해라
  • 매개변수를 전달하고자 할때에는 별표규칙을 따른다.
  • 메모리 할당시 변수의 크기를 결정하기 위해서 sizeof()를 사용해라

 

전역데이터 -  어디서나 접근 가능한 데이터라 편리할수 있다. 그러나 숙달된 사람은 지양한다

전역 데이터를 사용할때 발생하는 일반적인 문제점

  • 전역 변수에 대한 부주의한 변경
  • 전역 데이터의 기괴하고 이상한 별칭 문제들
  • 전역 데이터의 재진입코드 문제 - 다중 thread일때 심각한 문제를 야기할수 있다.
  • 전역 데이터로 인한 코드 재사용문제 - 전역 데이터 변경은 원본 프로그램에 영향을 미칠뿐 아니라 오래된 클래스를 사용하는 모든 새로운 프로그램들에게도 영향을 미친다.
  • 전역 데이터와 관련된 불확실한 초기화 순서 문제점 - 서로 다른 번역 유닛사이에서 데이터가 초기화되는 순서가 어떤 언어 특히 C++에서는 정의되어 있지 않다.
  • 전역데이터로 인해 모듈화와 지적인 관리 용이서으이 손상

 

전역데이터를 사용하는 이유

  • 전역적인 값을 보관한다
  • 명명된 상수를 흉내내라
  • 열거형을 흉내내라
  • 매우 자주 사용되는 데이터에 대한 사용을 능률적으로 처리하라
  • 뜨내기 데이터(tramp data)를제거한다

최후의 수단으로만 전역데이터를 사용해라

  • 각 변수들을 로컬로 만든 다음, 필요할 때에만 변수를 전역으로 만든다 -  처음에는 지역변수로 만들고 필요할때 private와 protected로 전역을 선언한다
  • 전역 변수와 클래스 변수를 구분하라
  • 접근 루틴을 사용해라

 

전역데이터 대신 접근 루틴의 사용

전역데이터로 할수 있는 것은 접근 루틴으로 더 잘할수 있다. 접근 루틴의 사용은 추상데이터 형을 구현하고 정보 은닉을 이루기 위한 핵심기술이다.

접근 루틴의 장점

  • 데이터에 대한 제어를 집중시킬수 있다.
  • 변수에 대한 모든 참조가 보호되고 있음을 보증할수 있다
  • 자동으로 정보 은식이 갖는 일반적인 이점들을 얻게 된다.
  • 접근 루틴은 추상데이터형으로 변환하기 쉽다.

접근 루틴의 사용방법

  • 모든 코드가 접근 루틴을 통해서만 데이터에 접근할수 있도록 한다
  • 모든 전역 데이터를 한곳에 집어 넣지 않는다
  • 전역 변수에 대한 접근을 제어하기 위해서 잠금(locking)을 사용해라
  • 추상화 수준을 접근 루틴에 만든다
  • 데이터에 대한 모든 접근을 동일한 추상화 수준에서 유지한다

 

전역 데이터를 사용할때의 위험요소를 줄이는 방법

전역 변수를 분명히 하는 이름 규약을 만든다

모든 전역 변수에 주석을 잘 작성한 목록을 만든다

중간 결과를 포함하기 위해서 전역변수를 사용하지 않는다

전역데이터를 사용하지 않는 것처럼 가장하기 위해 모든 데이터를 거대한 객체에 입력하고 모든 것에 전달하는 방법을 사용하지 않는다

 

요점 정리

  1. 구조체는 프로그램을 덜 복잡하게 하고 이해하기 쉽게 만들며 유지 보수 하기 쉽게 만드는데 도움을 줄수 있다
  2. 구조체 사용을 고려할때마다 클래스가 더 좋은 방법인지 고려한다
  3. 포인터를 오류를 발생할 가능성이 있다. 접근 루틴이나 클래스, 그리고 방어적인 프로그래밍 습관을 사용하여 보호한다
  4. 전역변수를 사용하지 않는다
  5. 만약 전역변수를 꼭! 사용해야 한다면 접근 루틴을 통해서 처리한다. 접근 루틴은 전역 변수가 제공하는 것 이상을 제공한다

 

이 글은 스프링노트에서 작성되었습니다.

by 무위자연 2008. 1. 19. 15:54