본문 바로가기

소프트웨어 개발방법론/애자일 방법론

애자일 코딩

새로운 프로젝트를 시작할 때, 코드는 이해하기 쉬우며 작업하기도 쉽습니다.

하지만 개발을 진행하면서 프로젝트륵 계속 수행하기 위해선 더 많은 개발자와 상당히 많은 작업량이 소요된다는 것을 느끼게 됩니다.

프로젝트 개발에서 평범한 압박이 나중에 스트레스로 오지 않게 하려면 가장 쉬운 방법은 코드를 잘 유지하는 것입니다.

 

- 의도적이고 의미있게 프로그래밍 하라

 

이해하기 어렵고 유지보수하기도 힘들며 에러가 있는 코드를 어렵지 않게 볼 수 있습니다.

코드가 어떻게 작동하는지 아무도 이해할 수 없다면 그건 아무 소용없는 코드입니다.

 

코드를 개발할 때, 항상 편리함보다 읽기 쉬움을 선택해야 합니다.

코드는 작성하는 것보다 훨씬 더 많이 읽히기 때문에, 코드 작성 시 성능이 좋지 않더라도 읽기 쉽다면 더 가치 있는 코드입니다.

(하지만 사실 이 말은 100% 맞다? 라고 보기엔 프로젝트의 성질에 따라 다를 수 있습니다)

 

새로운 기능을 추가하거나 버그를 수정하기 위해서 코드의 일부를 수정할 때, 체계적으로 코드에 접근하도록 노력해야 합니다.

우선 코드가 무슨 일을 하고 어떻게 동작하는지 이해한 후, 무엇을 고칠지 알아내 변경, 테스트 합니다.

그러려면 우선 이해가 쉽도록 코드를 작성해야 합니다. 아래 예시를 보도록 하겠습니다.

 

coffeeShop.PlaceOrder(2);

 

위와 같은 코드가 있다고 가정했을 때, 읽는 것만으로 어느정도 이해는 됩니다만 PlaceOrder 함수에 전달된 2 라는 파라미터가 대체 무슨 역할을 하는건지 파악이 어렵습니다. 그럼 이걸 좀 더 이해하기 쉽게 주석을 추가해보겠습니다.

 

coffeeShop.PlaceOrder(2 /* 큰 컵 */);

 

조금 낫긴 하지만 미흡한 코드를 보충하기 위해 주석을 사용한 경우고, 영 맘에 들지 않습니다.

(맘에 들지 않는다는건, 이 상황이 코드만으로 해결이 가능하기 때문입니다)

 

public enum CoffeeCupSize
{
  Small,
  Medium,
  Large
}

coffeeShop.PlaceOrder(CoffeeCupSize.Large);

 

위처럼 enum 을 정의한 후, 파라미터로 사용했던 2 를 CoffeeCupSize.Large 로 대체했습니다.

이로 인해 Large 사이즈의 커피를 주문하고 있다는 사실이 코드만으로 드러나게 되었습니다.

이렇게 항상 코드를 이해하기 쉽게 만들도록 노력해야 합니다.

 

• PIE 원칙

 

작성하는 코드는 의도를 명확히 전달하고, 반드시 의미가 있어야 합니다.

이렇게 하면 읽기 쉽고 이해하기 쉬운 코드가 될 것입니다. (PIE, Program Intently and Expressively)

 

코드를 너무 독창적이고 불명확한 것보다는 PIE 원칙을 따르는, 의도적이고 의미 있게 프로그래밍해야 합니다.

PIE 원칙을 어기면 코드의 가독성을 해치고 이해하기 어려워지며, 코드의 정확성에 나쁜 영향을 줄 수 있습니다.

 

코드를 작성할 때 의미가 잘 나타나도록 언어의 특징을 사용할 수도 있습니다.

의도를 전달하는 메서드 이름을 사용하고, 읽는 사람에게 메서드 매개변수의 목적을 이해시키기 위해 메서드 매개변수에 이름을 붙이도록 합니다. 또한 예외를 사용할 경우엔, 예외에 적절한 이름을 붙이도록 합니다.

좋은 코딩 규칙은 불필요한 주석, 다른 문서의 필요성을 줄이는 한편 훨씬 더 이해하기 쉬운 코드로 만드는 것입니다.

 

실제로 팀에서도 프로젝트 진행시 알기 어려운 함수나 클래스를 작성하고 (말 그대로 가독성이 떨어진다는 의미입니다) 몇줄의 주석으로 그것을 무마하려는 pr 을 수차례 본 적이 있습니다. 이런 상황이 정말 어쩔 수 없는 경우는 거의 없었습니다.

그러한 관점에서 생각해본적은 없지만 코드는 작성하는 것보다 훨씬 더 많이 읽힌다는 말에 깊이 동감하고, 코드를 단순하게 만드는데 더 많은 노력을 해야합니다. (복잡하면, 나중에 본인이 짠 코드마저 제대로 읽히지 않는 경우가 부지기수입니다)

 

- 코드로 대화하기

 

개발자는 두 가지 방법으로 코드를 문서화할 필요가 있습니다.

코드 자체로 표현하는 것과, 코드로 표현할 수 없는 부분에 주석을 사용하는 것 입니다.

 

위와 조금은 상반되는 이야기일수도 있지만, 코드로 잘 표현된 메서드라도 이해하기 위해 상당히 많은 코드 라인을 읽어야 할 때

해당 메서드의 동작을 잘 설명하는 주석 몇 줄은 개발자의 시간을 좀 더 유익하게 쓸 수 있도록 해줍니다.

메서드의 목적, 메서드가 의도한 결과는 무엇이고, 주의해야 할 점은 무엇인지 빠르게 파악할 수 있습니다.

 

• 숨기기 위해서 주석을 달지 말자

 

작성하는 대부분의 코드나 특히 메서드의 본문 안에 주석이 꼭 필요하진 않습니다.

개발자는 주석 때문이 아닌, 소스코드의 정밀함과 명료함 때문에 소스코드가 이해하기 쉽도록 해야 합니다.

그 중에서도 네이밍.

제가 개인적으로도 프로그래밍을 할 때 꽤나 시간을 들이는 부분인데, 이름을 부여하는 것은 굉장히 중요합니다.

 

잘 고른 이름을 사용해 코드를 읽는 사람에게 많은 의도와 정보를 전달할 수 있습니다.

부자연스러운 네이밍을 사용한다면 읽기 힘들고 이해하기 어려운 코드를 만들게 됩니다.

잘 선택한 이름과 명료한 실행 경로가 있다면, 코드에서 주석은 거의 필요 없습니다.

 

애매한 변수명은 피해야 합니다. 애매하다는 것은 꼭 변수명이 짧은 것만을 의미하진 않습니다.

예를 들어 많은 언어에서 루프 색인 변수로 i 를, 문자열은 s 를 널리 사용합니다.

이런 변수명은 여러 언어에서 관용적으로 쓰이기에 짧더라도 애매하지는 않습니다.

오히려 루프 색인 변수에 s 를 사용하는 것이 굉장한 애매함을 초래하게 되겠죠.

 

주석은, 코드를 읽는 사람에게 올바른 방향을 알려주는 일종의 로드맵을 만들기 위해 사용하면 유용할 것입니다.

코드 안에 있는 클래스와 모듈마다 클래스와 모듈의 목적, 특별한 필요조건과 같은 짧은 설명을 추가할 수도 있습니다.

 

• 목적 : 이 메서드는 왜 필요한가

• 선행조건 : 이 메서드는 어떤 입력이 필요하고 객체 내부 상태는 어떠해야 하는가

• 후행조건 : 메서드가 성공적으로 완료되었을 때 객체 내부 상태는 어떻고 반환값은 어떻게 되는가

• 예외 : 무엇이 잘못될 수 있고 어떤 예외를 던지는가

 

예를 들어 위와 같은 내용을 메서드에 주석으로 추가했을 때, 우리는 RDoc, javadoc, ndoc 과 같은 툴 덕분에 쓸모있고 잘 정동된 문서를 쉽게 얻을 수 있습니다. 이러한 툴은 주석을 가져다가, 하이퍼링크로 연결된 멋진 HTML 출력을 만들어 줍니다.

 

코드는 작성하는 것보다 항상 더 많이 읽혀질 것입니다. 따라서 작성할 때 코드를 문서화하는데 들이는 작은 노력은 큰 보상값으로 돌아올 수 있습니다.

 

- 능동적으로 트레이드오프 평가하기

 

프로젝트의 성공을 위해서 외관, 성능, 패러다임이 필수적인지 아닌지 고려하지 않고 무언가를 과도하게 강조하는 것은 오히려 프로젝트를 실패하게끔 만들 수 있습니다.

프로젝트의 성질마다 다르겠지만, 성능이 괜찮은데도 어플리케이션을 더 빠르게만 만들려고 노력하는 것보단 더 중요한 다른 면이 분명히 있습니다.

 

예를 들면 제가 현재 속한 회사의 주 아이템인 광고 비딩은, 빠를수록 좋지만 120ms 안에 응답하면 된다 와 같은 조건 또한 존재합니다.

이 경우 이미 충분한 응답 속도를 내고 있는 상황에서, 몇 ms 줄이기 위해 들이는 노력보다 더 좋은 광고풀을 확보하기 위한 알고리즘 개발, 광고 비딩 로그 유실 없애기 등의 부분에 시간을 쏟는 것이 더 중요할 수 있습니다.

 

과거와 달리 현재엔 하드웨어의 값이 나날이 싸지고 있기 때문에 대체로 성능은 이런 부분에서 커버가 되기도 합니다.

이렇게 절약한 시간을 어플리케이션의 다른 측면에 투자할 수 있습니다.

이런 적절한 트레이드오프를 통해 어디에 더 집중해야 하는지 결정해야 할 것입니다.

 

• 최선의 해결책은 없다

 

모든 환경을 만족시키는 최선의 해결책은 없습니다. 우리는 가까이서 문제를 평가하고 가장 적당한 해결책에 도달해야 합니다.

성능, 편의성, 생산성, 비용, 적절한 릴리즈등을 고려해 트레이드오프를 평가토록 합시다.

 

- 조금씩 코딩하기

 

몇 분 동안이라도 각자가 작성한 프로그램을 테스트해 제대로 진행되는지 확인하지 않고 코딩하는 습관은 버리는게 좋습니다.

조금씩 작성하고 테스트하는 지속적인 피드백을 기초로 코드를 작성하면, 코드를 개량하고 구조화하는데 조금씩 코딩하기가 충분한 도움이 된다는 것을 알게 될 것입니다.

 

조금씩 작성하고 테스트할 때 더 작은 메서드와 응집력이 더 큰 클래스를 만드는 경향이 있습니다.

또한 코드의 가독성을 위해 작업하게 될 수도 있고, 메서드를 더 작은 메서드로 쪼개서 더 쉽게 테스트가 가능하다는 사실을 깨닫게 될 수도 있습니다. 이렇게 작고 유용하게 유지하는 것이 애자일 접근 방식 입니다.

 

- 단순하게 유지하라

 

인터넷에 디자인패턴 이라고 검색만 해도 패턴에 대해 굉장히 많은 글들을 볼 수 있고, 개발자들은 이러한 패턴들을 자신의 코드에 늘 적용하고 싶어 합니다. 하지만 진짜로 코드에 패턴이 필요한지, 당면한 특정 문제를 이 패턴이 어떻게 도와줄지 먼저 확인해야 합니다.

코드를 과도하게 설계하고 복잡하게 만들고 싶은 강박에서 벗어나야 합니다.

 

많은 개발자들이 복잡성과 성과를 혼동하는 경향이 있습니다. 복잡하게 코드를 짜면, 그것을 마치 잘 만든 것 처럼 착각하는 것입니다.

잘 동작하는 단순한 디자인을 만드는 것에 더 자부심을 느껴야 합니다.

 

• 우아함이란 무엇인가?

 

우아한 코드는 유용성과 명확성에서 바로 명백하게 드러나야 합니다. 우아함은, 이해하고 인식하기는 쉽지만 만들기는 훨씬 어렵습니다.

 

• 단순함은 극단적일 정도의 단순화가 아니다

 

단순함이란 지나치게 간단하고, 아마추어 같고, 불충분함을 뜻하지 않습니다. 오히려 그 반대입니다.

단순함은 지나치게 복잡하고 뒤얽힌 해결책보다 훨씬 얻기 어렵습니다.

저도 한때는 너무 단순하고 눈에 보기 쉬운 코드가 좋지 않은 코드구나 라고 생각한 적이 있습니다.

근래엔 오히려 내 코드를 더 단순하고 직관적이게 만드는 것이 정말 어렵구나.. 라는 점을 더 생각하게 됩니다.

 

- 응집도 높은 코드를 작성하라

 

소프트웨어공학에서 너무도 유명한 말입니다. 결합도는 낮게, 응집도는 높게.

응집도는 컴퍼넌트의 멤버가 기능적으로 얼마나 관련되었는지를 나타내는 단위입니다.

높은 값의 응집도는 멤버가 하나의 기능이나 기능의 집합을 위해 일한다는 사실을 나타냅니다.

 

클래스를 만들려고 할 때, 이 클래스가 제공하는 기능이 컴퍼넌트에 이미 있는 다른 클래스의 기능과 비슷하고 밀접하게 관련되어 있는가?

이것이 바로 컴퍼넌트 수준의 응집도입니다.

 

클래스 역시 응집도의 대상입니다.

메서드와 속성이 하나의 기능을 구현하기 위해 같이 작업한다면, 이 클래스는 응집도가 높다고 할 수 있습니다.

 

낮은 응집도를 갖는다면 유지보수등에 아주 큰 문제를 겪을 수 있습니다.

서로 다른 다섯 가지 기능을 구현하는 클래스를 예로 들겠습니다. 만약 다섯 가지 기능 가운데 하나의 요구사항이나 상세 구현이 변경되면 이 클래스를 변경해야 합니다. 하지만 각 클래스를 둬서 기능 하나씩만을 구현하게 한다면, 다른 클래스를 건드릴 일이 없습니다.

비슷하게 응집력이 더 큰 컴퍼넌트는 수정해야 하는 이유가 더 적어서 훨씬 더 안정적입니다.

 

'단일 책임 원칙' 에 따르면, 모듈은 변경에 대해 단 한 가지 이유만을 가져야 합니다.

 

- 묻지 말고, 말하라

 

절차적인 코드는 정보를 얻고 나서 결정을 하고, 객체지향 코드는 객체에게 행동을 하게 합니다.

하지만 이 이야기는 객체지향 패러다임에 한정된 것이 아니고, 어떤 애자일 코드라도 이 방법을 따라가야 합니다.

 

호출자로서 호출된 객체의 상태를 기초로 결정해서는 안되며, 호출된 객체의 상태를 변경해서도 안됩니다.

개발자가 구현하는 로직은 호출된 객체의 책임입니다.

객체의 외부에서 결정하게 한다면 객체의 캡슐화를 위반하고 버그를 일으킬 수 있습니다.

 

- 계약에 의해서 교체하기

 

시스템을 유연하게 유지하는 핵심적인 비법은 기존의 코드가 그 차이를 알아차리지 못하게 새로운 코드가 기존의 코드를 대신하는 것입니다.

 

'리스코프 교체 원칙' 은 다음과 같은 사실을 알려줍니다.

 

• 사용자가 차이를 알 필요 없이 상속 받은 클래스 객체는 부모 클래스 객체가 사용되는 곳에서 교체할 수 있어야 한다.

(부모 클래스의 메서드를 사용하던 코드는 수정 없이 상속받은 클래스의 객체를 사용할 수 있어야 한다)

 

utils = new BasicUtils();
...
sortedList = utils.sort(aList);

---

utils = new FasterUtils();
sortedList = utils.sort(aList);

 

예를 들어 위와 같은 코드에서, FasterUtils 클래스가 BasicUtils 를 상속 받았다고 할 때 FasterUtils 객체는 BasicUtils 객체와 완벽하게 교체가 가능합니다. utils.sort() 함수 또한 잘 작동할 것입니다.

허나 만약 정렬의 의미를 바꾼 BasicUtils 의 서브 클래스를 만들었다면, 이 교체 원칙을 어긴 것입니다.

 

교체 원칙을 따르기 위해 상속된 클래스의 서비스는 대응하는 부모 클래스의 메서드보다 더 많이 요구하거나 더 적게 약속해서는 안됩니다.

상속된 클래스는 자유롭게 교체할 수 있어야 합니다. 이는 클래스 상속 계층을 설계할 때 아주 중요한 사항입니다.

교체 원칙을위반하면 상속 계층이 코드 재사용성을 여전히 제공해도 확장성에는 별 도움이 되지 않을 것입니다.

 

• is-a 는 상속을 사용하고, has-a 나 uses-a 에는 위임(Delegation) 을 사용하자

 

상속을 사용할 때, 상속 받은 클래스가 부모 클래스의 자리에 교체할 수 있는지 확인해야 합니다.

만약 아니라면, 왜 상속을 사용하려는지 다시 생각하고, 만약 그 답이 새로운 클래스를 만들 때 부모 클래스의 코드를 재사용하는 것이라면 상속 대신 복합 연관(composition) 을 사용해야 합니다.

복합 연관에서는 클래스 객체가 다른 클래스의 객체를 포함해서 사용하며, 포함된 객체에게 책임을 위임합니다.

 

한번 더 정리하자면, 상속과 위임을 사용해야 하는 때는 아래와 같습니다.

 

• 새로운 클래스를 기존의 클래스 자리에서 사용할 수 있고 이들 사이의 관계가 is-a 로 표현될 수 있으면 상속

• 새로운 클래스는 단순하게 기존의 클래스를 사용해야 하고, 관계가 has-a 나 uses-a 로 표현할 수 있으면 위임

 

사실 이 부분은 책의 내용을 거의 그대로 가져왔습니다. 저는 개발간에 상속은 자주 사용했지만, 위임과 복합 연관 이라는 개념은 생소합니다. 책에서는 위임은 언제나 상속보다 바람직 하다고 강조하는데, 이 부분이 정말 맞는지 차후에 따로 정리해보도록 하겠습니다.

 

 

출처 : 애자일 프랙티스(책)

'소프트웨어 개발방법론 > 애자일 방법론' 카테고리의 다른 글

애자일 협력  (1) 2021.06.12
애자일 디버깅  (0) 2021.06.05
애자일 피드백  (0) 2021.05.22
애자일에서 고객과의 협력  (0) 2021.05.15
애자일 기르기  (0) 2021.05.08