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

by 무위자연 2008. 1. 25. 04:34
  • "gadget이란 단어를 찾다가 확 민망해졌쓰" (해리포터 소서러 필로소퍼) 2008-01-22 08:36:45
  • 지누션 4집 노라보세의 보이네가 리메이크 인줄은 동현이는 정말 몰랐네 ㅋ 나미씨의 노래구나! ㅋ (보이네 지누션 노라보세 리메이크 나미) 2008-01-22 12:23:10

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

by 무위자연 2008. 1. 23. 04:36
  • 늦잠자고 나왔더니 왠 눈발이 이리도 날리냐-_- 2008-01-21 08:26:45
  • What's your regular commute? 보통 뭐타고 출퇴근해요? (영어한마디) 2008-01-21 17:42:26
  • DateUtil.compareDates(tmpDate, dataStartDate) 왼쪽 날짜가 크면 -1을 오늘쪽 날짜가 크면 1을 같으면 0을 리턴한다. 이게 왜이리 헷갈리는 것일까나-_- 좌절이얌 2008-01-21 17:46:53

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

by 무위자연 2008. 1. 22. 04:35
  • 어제는 집에서 쉬는건데.. 민형옹, 승훈옹, 다운이, 소미랑 마시는게 아니었쓰 ㅋ 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