본문 바로가기

카테고리 없음

소프트웨어개발론 - 코딩01

코딩 로드맵 (p. 12)

소프트웨어 제품의 품질은 결국 원시 코드에 모두 귀결된다.

1. (각 프레임워크 패키지 혹은 응용 패키지에 대하여) 코딩 표준 정의

- 아키텍처

2. (모든 클래스에 대하여) 메소드 구현

- 요구사항

- 상세 설계 의사 코드 흐름도

3. 클래스 인스펙션(검사)

4. 단위 테스트

5. 통합을 위해 릴리스(공개)

 

코딩 작업

- 설계 명세에 나타낸 대로 요구를 만족할 수 있는 프로그래밍

- 오류가 적은 품질 좋은 프로그램

작업 과정

1. 원시 코드를 같은 스타일로 만들기 위해 코딩 표준 만들기

2. 아키텍처 설계 결과를 기반으로 프레임워크 패키지(사용할 기술)와 응용 패키지(내부 구조)를 결정

3. 클래스 구현이 끝나는 대로 인스펙션

4. 클래스 단위로 테스트

5. 클래스나 패키지를 릴리스하여 응용 시스템으로 통합

 

좋은 소프트웨어 코드의 6가지 공통점

1. 쉽게 읽을 수 있음 (가독성)

라이온 브릿지사의 선임 소프트웨어 엔지니어 - 루크 번햄

- 5분 이내에 개발자의 의도를 이해할 수 없는 코드는 좋은 코드가 아님

- (컴퓨터와 달리) 사람은 변수의 이름이나 줄 간격을 고려함

- 한번 개발된 코드는 수명 주기 동안 수백 차례 읽힘

- 코드의 가독성을 높이기 위해 의미 있는 변수 이름 사용

- 간격을 삽입하거나 적절한 코드 구조를 표현하기 위해 들여쓰기 사용

 

고우고우사의 선임 애플리케이션 개발자인 닐 베스트

- 가능한 자주 줄 바꿈 하는 것이 원칙

- 개인적으로 선호하는 스타일

- 행보다는 열이 긴 것을 선호

- 줄을 늘리고 싶은 게 아니라, 가독성을 높이기 위해서 사용

- 함수의 알규먼트가 2개라면 2줄로, 산술식의 변수가 여러 개라면 여러 줄로 작성

- 코드를 읽는 사람이 번거로이 느낄 수 있지만, 그만한 가치가 있음

 

2. 주석이 잘 작성되어 있음

번햄

- 루프가 뭔지 말해 주는 주석은 필요 없음

- 코드가 제공하는 기능(= 해당 코드가 왜 필요한가?)을 말해 주는 주석이 필요

- 좋은 코드에는, 개발자가 코드를 개발한 목적을 설명하는 주석이 항상 존재함

훗날 코드를 다시 들여다 봤을 때, 개발할 당시 어떤 생각을 했는지 깜빡하는 경우를 대비해야 함

- 단 한 사람이라도 해당 코드의 이름과 정보가 포함된 주석을 이용한다면 주석의 가치는 충분하며, 코드 수정 작업이 훨씬 수월하고 간결해짐

- 내 경우에는 코드에서 무슨 일이 벌어지고 있는지 설명하기 위해, 간결한 주석을 넘어 문학적인 프로그램 패러다임을 적극적으로 활용해야 함

 

3. 코드 구조가 간결함

프로그래머 모일란 - 각 코드가 각자의 기능을 훌륭히 수행하고, 다음 코드가 다른 기능을 수행하도록 프로그램을 만들어야 한다. 가장 간단한 솔루션이 최고의 솔루션이다.

익명의 웹 개발자 - 코드를 지나치게 중복해서 사용하는 것을 경계해야 한다. 짧지만 확실하게 한 가지 기능을 수행하도록 만들어야 한다.

코드를 간결하게 만들면 버그를 줄일 수 있음 - 코드의 복잡성이 높아지면, 버그의 수도 많아질 수 있다는 상관관계를 통계적으로 입증 

 

4. 변경에 탄력적임

번햄 - 현재 필요한 사항, 미래에 필요할 수 있는 사항 모두를 염두에 둔 소프트웨어가 좋은 소프트웨어이다. 최소한의 수정으로 미래에 필요한 사항을 수용할 수 있도록, 탄력적인 소프트웨어를 개발할 수 있어야 한다.

 

5. 활용을 위해 관리될 수 있어야 함

모일란 - 누군가 버그를 수정할 수 있어야 한다. 당신이 버그를 수정하는 관리자가 될 수 있으므로, 코드는 쉽게 수정할 수 있도록 만들어야 한다.

관리성

- 어떤 코드든 삭제되거나 무시되는 일 없이, 추후 활용될 수 있게 관리돼야 함

- SW 코드 내 URL, 액세스 키, DB password, 특정 사용자의 IP 주소 등을 직접 하드 코딩하는 방식으로 개발해서는 안 됨

- 개발자가 자신의 코드를 관리할 수 없다면, 모듈화가 충분히 이뤄지지 않은 코드임

 

6. 제기능을 해야 함

모일란 - 코드가 원래 목적한 기능을 하는 것이 가장 중요하다.

번햄 - 아무리 좋게 보이는 코드여도 제기능을 못하면 좋은 코드가 아니다.

개발자가 자신의 코드를 관리할 수 없다면 모듈화가 충분히 이뤄지지 않은 코드임.

코드 배포 전에, 충분히 제기능을 수행하는지 점검해야 함.

 

좋은 코드 작성을 위한 13가지 규칙

1. 최적화보다는 가독성을 우선으로

- 코드는 항상 읽기 쉽고 이해하기 쉽게 작성해야 함

- 가독성이 높은 코드를 만들기 위해 모듈화 개념이 잘 반영된 코드를 작성해야 함

- 이러한 모듈화 구조는 실행 관점에서 볼 때, 종종 최적화에 위배됨

- 그럼에도 가독성 있는 코드를 작성하는 것이 SW 전체 수명 주기를 고려한다면 비용 측면에서 효과적임

 

2. 아키텍처를 우선 개발

- 아키텍처 설계 없이 코드를 개발한 경우, 대개 코드의 구조적인 문제가 발생함 - 목표 달성을 위한 계획 없이 그냥 열심히 하는 것과 같은 맥락

- 아키텍처는 시스템을 어떻게 모듈(조각)로 나눌지 결정하므로, 모듈화 작업과 아키텍처 설계는 서로 밀접하게 연결돼 있음

 

  • 아키텍처 = 시스템을 여러 모듈로 나누는 큰 설계
  • 모듈화 과정 = 실제로 기능을 분리하고 인터페이스를 정의하는 일

 

- 아키텍처가 좋으면 변경하기 쉽고, 테스트하기도 쉬운 구조가 됨 - 모듈 간의 input/output이 명확하게 정의되기 때문

 

다음 이해는 아키텍처를 기반으로 이뤄짐

- 코드를 작성하기 전에 선행해야 할 작업

- 코드 사용 방법

- 모듈화 방법

- 서비스 동작 방법

- 구조

- 테스트 및 디버깅 방법

- 업데이트 방법

 

3. 테스트 커버리지 고려

- 테스트는 결함을 제거하고 좋은 품질을 개발하는 데 중요한 역할을 수행함

 

소프트웨어 개발에서 테스트가 필요한 경우

- 최소 한 달은 변경하지 않을 모듈이나 마이크로서비스를 개발하는 경우

- 오픈 소스 코드를 작성하는 경우

- 핵심 코드 또는 금전적인 부분과 맞닿는 코드를 작성하는 경우

- 코드 업데이트와 동시에 테스트 업데이트를 할 수 있는 충분한 비용이 있는 경우

 

테스트가 필요 없는 경우

- 스타트업에서 홍보용 소프트웨어를 개발하는 경우

- 팀이 작고, 코드가 빠르게 변하는 경우

- 출력값을 보고 수동으로 간단하게 테스트할 스크립트를 작성할 수 있는 경우

 

4. 간단하고 단순하게 코딩 하라

- 이해하기 어렵고 복잡한 제어 구조를 가지는 코드를 작성해서는 안 됨

- KISS(Keep It Simple and Stupid) 원리를 지킬 것

- 코드를 단순하고 간단하게 작성 -> 버그가 줄어들고 디버깅 시간도 줄일 수 있음

- 불필요한 추상화/패턴 적용 없이 필요한 일만 수행하도록 코드 작성

- 추상화나 패턴을 적용하면 코드에 대한 가독성이 높아지고 유지보수가 쉬워질 수 있으나, 과도하게 적용하면 오히려 코드가 더 복잡해짐

 

5. 주석을 작성하되 보조적으로 사용하라

- 좋은 코드는 주석이 없어도 이해되는 코드

다른 개발자들의 코드를 다루는 시간을 절약하는 방법은?

- 메소드의 정의와 사용법을 설명하는 한 줄짜리 간단한 주석(문서화) 작성

  > 새로운 개발자가 코드를 이해하는 시간을 상당히 줄여 줄 수 있음

  > 더 많은 사람에게 해당 코드를 재사용하고 수정할 기회를 제공

 

6. 강한 응집과 느슨한 결합을 추구하라

- 마이크로서비스 아키텍처 사용

  - 매우 다양한 환경에서도 SW가 잘 동작하도록 지원할 수 있음

  - SW를 여러 서버로 분산할 수 있을 뿐 아니라, 단일 서버 내에 분산 처리(프로세스 분산)도 가능함

- 이러한 효과를 얻기 위해 마이크로서비스는 구성 모듈 간 강하게 결합되어야 하며, 서비스 간에는 느슨하게 결합해야 함

 

7. 코드 리뷰가 항상 좋은 것은 아니다

- 코드 리뷰: 코드에 내재된 오류를 찾는 것이 목적

코드 리뷰가 의미 없는 이유

- 개발자들이 작성되는 코드를 대체로 이해하고 있으며, 개발 대상 응용 영역에도 친숙하다면 코드 리뷰는 필요 없음

- 시스템을 이해하는 수단이나, 다른 팀원을 가르치려는 목적으로 코드 리뷰를 하는 것은 의미가 없음

 

8. 리팩토링(코드 내부만 바꾸고 기능은 바꾸지 않는 작업)은 하지 않는다

- test case가 이미 존재한다는 가정 하에 사용됨 (test case를 동일하게 수행한다는 것 = 수정 전/후가 동일하게 작동된다는 것, 즉, 코드 내부를 변경한 후에도 기능은 그대로 유지됨)

- 소프트웨어 개발 과정에서 코드의 체계성을 무시하고, 기능 실행에 초점을 맞춰 개발하는 경우가 있음

  - 추후 리팩토링을 통해 코드 구조를 개선하고자 하는 의도

  - 나중에 큰 기술적 부담으로 돌아오거나, 모든 코드를 다 삭제한 처음부터 다시 작성하게 되는 상황이 종종 발생함

- 프로토타입을 단시간에 개발하고자 한다면, 본 시스템을 개발하는 과정에서 리팩토링이 적용될 수 있는 여지는 있음

- 구조화된 코드를 작성할 수 있다면, 굳이 추후에 리팩토링할 필요가 없음 (기술적 부채, Technical Debt의 관점에서 이해 가능)

*기술적 부채(Technical Debt): 빠르게 개발하려고 당장 편한(비효율적이거나 임시방편) 코드를 선택했을 때, 나중에 그 부작용을 해결하기 위해 치르게 되는 ‘추가 비용’을 빚에 비유한 개념.

+ 대규모 프로젝트에서는 애자일 방법을 비선호함. 리팩토링은 애자일에 포함됨.

+ 애자일과 UML은 같은 영역(포함되는 것)이 아니지만, 구조가 비슷하기 때문에 같이 배움.

 

9. 피곤하거나 컨디션이 좋지 않을 때는 코딩 하지 마라

하루 6시간이 적당

 

10. 모든 것을 한 번에 작성하지 말고, 반복적으로 개발하라

- 사용자 혹은 클라이언트가 정말로 필요로 하는 것이 무엇인지 분석하고 예측한 후, 짧은 기간 동안 개발할 수 있는 가장 중요한 피쳐 선택

- 한 번에 모든 시스템을 구현하려고 하는 것은 품질을 떨어뜨리는 원인임

 

11. 가능하면 자동화 도구 사용

- 5분 정도의 작업을 1분 이내로 줄여서 수행 가능 -> 비용 절감, 품질 향상

 

그 외 취미 가지기, 여유 시간에 새로운 것 배우기가 있다.

 

연관 관계 구현

클래스 A와 B 사이에 1대1 연관 관계가 있다.

- 1대1 연관: A에서 B 함수를 호출할 필요가 있다면, A가 B에 대한 참조를 갖도록 구현 / 반대로 B에서 A의 함수를 호출할 필요가 있다면, B가 A에 대한 참조를 갖도록 구현

- 1대N 연관: 클래스 A에서 인스턴스 B의 메소드를 호출할 것이 있다면, 클래스 A가 클래스 B의 참조를 모음으로 갖도록 구현

- N대N 연관: 중간에 연관 클래스를 도입하여 1대N의 관계로 바꾸어 설계하기도 함

 

시퀀스 다이어그램 (p. 37~)

- 일관성을 어떻게 지키는지가 체킹 포인트

- A 객체에서 B 객체로 메시지가 향하면, 메소드는 B 클래스에 정의함

 

코딩 표준

- 대표적인 기준은 간결하고 읽기 쉬운 것

  - 간결함: 복잡하지 않고 명확하여 이해하기 쉬운 것

  - 읽기 쉬움: 프로그램을 대충 훑어보거나 이해하기 쉬운 것

- 설계에서 모듈화의 목표, 높은 응집력, 낮은 결합도를 달성하였다면 모듈은 간단해짐

 

MISRA-C 코딩 표준

- 1998년 유럽의 자동차 연합에서 자동차 탑재 SW의 신뢰성 및 호환성을 높이겠다는 의도로 개발한 C 프로그램 코딩 표준

- 총 규칙 수 159개 (기존 표준의 문제점과 불명확한 부분을 구체화하는 과정에서 규칙 수 증가함)

 

명명 규칙

카멜 케이스 ex. HelloWorld

- 상수는 모두 대문자로 ex. SPEED_OF_LIGHT = 12345;

- 클래스와 인터페이스 이름은 명사 또는 명사구 -> 대문자로 시작 ex. interface AqueousHabitat, class FishBowl

 

메소드 이름

- 동사구는 소문자로 시작 ex. setTitle

- 함수 이름은 값을 설명하는 명사구로 ex. areaOfTriangle

- 필드(ex. title)의 값을 접근하여 리턴하는 함수는 get으로 시작 ex. getTitle

- 조건을 묻는 bool 함수의 이름은 is로 시작 ex. isEquilateralTriangle

 

변수 이름

- 일반적으로 소문자로 시작

- 용도에 대한 힌트를 제공해야 함

- 모호한 이름을 사용하지 않아야 함

- 변수 이름의 길이는 대상이 사용된 위치를 고려

  - 매개변수: 되도록 짧게

  - 필드 변수: 가능한 길고 의미가 담기게

 

패키지 이름

- 일반적으로 모두 소문자이며 명사로 지어야 함

- 클래스명 등 기존 식별자와 다르게 이름을 붙일 것

 

p. 50~57 사진 참조

 

프로그래밍 취약점 목록 (이름을 보고 뜻이 뭔지 말할 수 있어야 함)

CWE (Common Weakness Enumeration) - 회사에서 중시하는 스탠다드

- CWE-658: C언어로 작성된 SW에서 발생 가능한 취약점 목록

- CWE-659: C++ 언어로 작성된 SW에서 발생 가능한 취약점 목록

- CWE-660: JAVA 언어로 작성된 SW에서 발생 가능한 취약점 목록

- CWE-1006: 잘못된 코딩 습관과 관련된 취약점 목록

- CWE-398: 코드 품질 저하를 유발하는 취약점 목록

- CWE-310: 데이터 보안 및 무결성과 관련된 암호화 이슈에 대한 취약점 목록

 

다양한 방법으로 발견되지만, 흔히 발견되는 오류들을 정해져 있음 - 웬만한 건 정적 도구로 해결 가능함

메모리 누수

- 메모리가 Free 되지 않고 프로그램에 계속 할당되는 현상

- 장기로 수행되는 시스템에서는 치명적인 영향을 줄 수 있음

ex. output = (char *) malloc (size); 선언 후 free 안 함 (해지 안 함) -> 메모리 누수 발생

 

중복된 프리 선언

- 프로그램 안에서 사용하는 자원은 먼저 할당되고, 사용한 후 free로 선언함

- 이미 free 선언된 자원을 또 다시 free 선언할 경우 오류

ex. free(output); free(output);

 

NULL 사용

- NULL을 포인트 하고 있는 곳(= 즉, 아무것도 가리키지 않는 변수)의 콘텐츠를 접근(= 해당 변수를 읽으려고)하려고 하면 오류 발생 (-> 데이터가 비어 있는데, 비어 있는 데이터를 읽으려고 하니 오류가 발생하는 것)

- 이러한 오류는 시스템을 다운시킴

 

별칭 남용

- 별칭은 많은 문제를 야기함

- 서로 다른 주소 값을 예상하고 사용한 두 개의 변수 값이, 별칭 선언으로 인해 같은 값이 되었을 때 오류가 발생함

 

배열 인덱스 오류

- 배열 인덱스가 한도를 벗어나면 예외 오류가 발생함

ex. dataArray[80];

for(i=0; i<80; i++) ...

i 배열은 0부터 79까지만 가능 -> for에서 한도를 벗어남 -> 예외 오류

이렇게 되면 배열 인덱스가 음수 값을 가지는 오류가 발생함

 

수식 예외 오류

- 0으로 나누는 오류

- 부동 소수점 예외 오류

  - 부동 소수점은 정확한 값을 가지고 있지 않음

  - 부동 소수점 오차로 인해 기대값이 달라질 수 있음

  - 등호 등 사용 불가

 

하나 차이에 의한 오류

- 0으로 시작해야 할 것을 1로 시작

- <=N으로 써야 할 곳에 <N을 쓴 경우

- 컴파일러나 테스트 도구에 의해 검출되지 않는 경우가 많음

 

사용자 정의 자료형 오류

- 오버플로우나 언더플로우 오류가 쉽게 발생함

- 사용자 정의 자료형의 값을 다룰 때는 특별한 주의가 필요함

 

스트링 처리 오류

- 우선 스트링 처리 함수에는 strcpy, sprint 등이 있음

 

- 매개변수가 NULL일 경우

문자열 처리 함수의 인자로 전달된 포인터가 NULL인 경우, 해당 함수는 NULL이 실제 메모리를 가리키는 주소라고 가정하고 문자열 연산을 수행하려 함.
그러나 NULL은 “어떤 메모리도 가리키지 않는 포인터”이기 때문에, 이 상태에서 문자를 읽거나 쓰려고 시도하면 프로그램은 유효하지 않은 메모리 영역에 접근하게 됨.
=> 그 결과 런타임 오류NULL 포인터 역참조(NULL pointer dereference) 문제가 발생하며 프로그램이 즉시 종료될 수 있음.

 

- 스트링이 NULL로 끝나지 않았을 경우

C 문자열은 반드시 마지막에 '\0' 문자(= NULL)가 포함되어 있어야 함. 
하지만 잘못된 초기화나 버퍼 오염 등으로 인해 문자열 끝에 NULL 문자가 누락될 수 있음.
이 경우, 문자열 처리 함수는 문자열의 끝을 찾을 수 없기 때문에 메모리를 계속 읽어나가며 배열 범위 밖의 데이터를 접근하게 됨.
이로 인해 예상치 못한 문자열 출력, 잘못된 값 처리, 혹은 심각한 경우 메모리 침범으로 프로그램이 중단되는 오류가 발생할 수 있음.
=> 즉, 문자열 종료 표시가 없으면 함수는 어디까지 문자열인지 판단할 수 없어서 위험한 메모리까지 접근하게 됨.

 

- source 매개변수의 크기가 destination 매개변수보다 크지 않을 경우

문자열 복사 함수(strcpy, sprintf 등)는 source 문자열의 길이가 destination 버퍼에 비해 더 긴지 여부를 자동으로 확인하지 않음.
따라서, 더 큰 source 문자열을 작은 destination 버퍼로 복사하려 하면 남는 문자열이 destination 배열 경계를 초과하여 메모리를 덮어쓰게 됨. => 버퍼 오버플로우(buffer overflow)
*버퍼 오버플로우: 프로그램의 변수값이 손상되거나, 메모리 구조가 깨져 비정상적 동작을 유발하는 오류 - 보안 취약점으로 악용될 수 있음

 

버퍼 오류

- 프로그램이 버퍼에 복사하여 입력받으려 할 때, 입력 값을 고의로 아주 크게 주면 스택의 버퍼에 오버플로우 발생함

- 버퍼 오버플로우를 이용해 해커들이 자신의 코드를 실행시킬 수 있음

 

동기화 오류

- 공통 자원을 접근하려는 다수의 스레드가 있는 병렬 프로그램에서 흔히 발생함

- 데드락(Deadlock): 다수의 스레드가 서로 자원을 점유하고 릴리스 하지 않는 상태

- 레이스 컨디션: 두 개의 스레드가 같은 자원을 접근하려 하여 수행 결과가 스레드들의 실행 순서에 따라 다르게 돼 있는 경우

- 모순이 있는 동기화 - 공유하는 변수에 접근 시 로킹과 언로킹을 번갈아 하는 상황에서 오류 발생

*로킹: 동시에 접근하면 안 되는 데이터(공유 변수)를 잠가두는 행동

 

시큐어 코딩

- 구현(개발) 단계에서 해킹 등의 공격을 유발할 가능성이 있는 잠재적인 보안 취약점을 사전에 제거하는 방법

- 외부 공격으로부터 안전한 소프트웨어를 개발하기 위한 프로그래밍 기법

- Gartner: 릴리즈 이전에 SW 취약점을 50% 줄이면, 침해사고 대응 비용이 75% 감소

-  NIST: 92%의 보안 취약점이 네트워크가 아닌 애플리케이션에서 발견됨, 릴리즈 이후에 오류를 수정하려면 30,000달러 비용 소요 - 그러나 개발 중 오류를 수정하기 위해서는 5,000달러면 충분함

- IBM: 릴리즈 이후 오류를 수정할 경우, 설계 단계보다 100배 증가

 

시큐어 코딩은 소프트웨어의 안전성, 신뢰성, 보안성 향상이 목적이며, 규칙과 권고로 구성됨.

코딩 규칙별로 제시된 세 가지 항목에 대한 정량화된 척도를 제시함

- 심각성 Severity: 규칙을 무시하면 얼마나 심각한 문제를 유발하는가의 척도

1 = 낮음 (서비스 거부 공격, 비정상 종료)

2 = 중간 (데이터 무결성 위반, 의도하지 않은 정보 공개)

3 = 높음 (임의 코드 실행)

 

- 발생 가능성 Likelihood: 규칙을 무시함으로써 생길 수 있는 결함으로 인해 취약점이 발생할 가능성에 대한 척도

1 = 예상 밖

2 = 아마도

3 = 가능성 높은

 

- 교정 비용 Remediation: 규칙을 준수하기 위해 요구되는 비용의 척도

1 = 높음 (수작업에 의한 탐지 및 수정)

2 = 중간 (자동 탐지 / 수작업 교정)

3 = 낮음 (자동 탐지 및 자동 교정)

 

CERT C 코딩 표준은 p. 76 확인

 

코딩 내용 정리

- 소스 코드를 개발하는 과정에서 지켜야 할 규칙은 매우 많음

- 규칙을 제공하는 목적은 1. 사용자 서비스를 제공하는 과정에서 발생하는 취약점 예방 2. 코드 자체의 품질 향상을 통해 확장성 및 유지보수성을 높이기 위함

- 다양한 코딩 규칙을 준수하면서 프로그램을 개발하는 일은 쉽지 않음

- 최근 이러한 규칙을 준수하는 코드가 자동 생성되는 방향으로 진화 중

 


오픈 소스 관련 내용은 ppt 참고...