[[ blog 이사 과정에서 정확한 posting날짜가 분실됨. 년도와 분기 정도는 맞지 않을까? ]]
 

Followings are all my personal opinion!

We always tell "good code". And lots of constraints are suggested for so-called "good code". For example, "number of line in one function should be less than xx", "number of class members should be less than xx", and so on.

Let's think from the basic. What is "good code". We don't need messy conditions for this. Just one factor!.
I want to define "good code" as "code that is easy-to-understand". Codes that is easy-to-understand, are easy to modify too.

Let's talk about this more.
"code that is easy-to-undertand" means, "It should be easy to know that the codes for and to predict the execution result.". Then what we should do to acheive this?

"Making code be small" can be an option. Anyway, small codes are easy. But, without reducing features and functionalities, decreasing code size enough is impossible. So, in practice, the is not a solution.

Let's convert the sentence into "Making stuffs required to be understood, be small". How about this? We can use lots of reliable module/component to do this. For example, let's think about standard library. We don't try to understand inside of each standard library functions because, we already trust the library enough. So, after understand inputs and outputs, we just use it. If we should understand inside of all those library functions, it will be nightmare. Too many things to understand!

In this context, we can say OOP(Object Oriented Programming), component-based software engineering, modularity, and so on. Making reliable and reusable modules/components can reduce stuffs required to be understood. Knowing input and output is much simpler than understanding inside codes of it.

This is my definition about "good code"

[[ blog 이사 과정에서 정확한 posting날짜가 분실됨. 년도와 분기 정도는 맞지 않을까? ]]

* Space vs. Speed
- Most famous one. (Memory space vs. execution speed) - details are omitted.

* Modularity vs. (Performance & Space)
- Well-designed-module should be stable. To be stable, there may be lots of 'parameter check', 'condition check', 'sanity check' and so on. Besides, for the safety reason, it may not trust passed memory space. So, it may allocates it's own memory space and try to copy data from passed memory to use it. Those make this trade-off. Well-designed-module hides it's information and stable as it is. But to be like this, it consumes speed and space.

* Abstraction vs. Debugging
- When software is stable, abstraction reduces maintenance costs very much. But, if not, debugging is more difficult than straightforward designed software, especially, to the engineer who are not accustomed to the software.

* low possibility to misuse vs. various and rich functionality
- It is clear...

* Backward-Compatibility vs. Innovation
- ...

[[ blog 이사 과정에서 정확한 posting날짜가 분실됨. 년도와 분기 정도는 맞지 않을까? ]]

In general, handling exception smoothly is very difficult. So, in many software, there are lots of prematured routine for exception handling. My recommend is, "just put "Assert" instead of premature exception handling(henceforth PEH)".

PEH give noisy information when exception is occurred, because software crashes at unexpected place due to PEH (Not the place where exception is actually raised).
Let's see following codes.

...
info = Get_personal_info(name);
if(!info){ return SYS_Fail;}
...
Personal_InfoT* Get_personal_info(char* name)
{
    ...
    int index = Search_with_name(name);
    if(index < 0) {return NULL;} // fail. (exception handling part)  ---- (*)
    ...
}

In this case, the place where exception is raised at first is (*). But, to handling exception, this function returns just 'NULL'. And at caller, it also returns 'SYS_Fail'. OK. It's reasonable. But, in practice, handling all these returned error smoothly requires lots of costs. So, in many cases, this is handled prematurely. And eventually, this error leads to software crash at unexpected place.

So, again, my point is just put 'Assert'. After all, we should try to debug all errors. 'Assert' is very helpful in debug. And then, we should decide whether remaining as 'Assert' or handling exception later - usually, exception from IO should be handled. This is more efficient in my opinion.

[[ blog 이사 과정에서 정확한 posting날짜가 분실됨. 년도와 분기 정도는 맞지 않을까? ]]
 

Output files should be separated from input files. Usually, 'clean' option is supported to delete auto-generated-output-files. But, separating outputs at directory level is better - for example, 'out' directory is root directory for output.
This is very basic and fundamental. But, this also easily ignored.

Supporting 'clean' option is good. But, cleaning output files those are scattered over several different places, is difficult to maintain. And, in this case, to find some necessary output files is also difficult.
On the environment that output files are separated at directory level, programmer can tell easily that which is input and which is output. This means, he/she can know which are necessary files to build software without further efforts - easy to read software.

[[ blog 이사 과정에서 정확한 posting날짜가 분실됨. 년도와 분기 정도는 맞지 않을까? ]]

In general, there is a table to help estimate schedule and this table usually categorize software according to software type(system product, business product and so on) and size(line of code - 10,000/20,000/.../500,000/... ). It is definitely very useful.
Now it's time to make table for me, my team and our company for more accurate estimation!! (We can use more customized categorization.)

[[ blog 이사 과정에서 정확한 posting날짜가 분실됨. 년도와 분기 정도는 맞지 않을까? ]]

In case that several branches are maintained at the same time, 'change list'(ex. bug fix) tends to be patched to several different code branches. General way to apply 'change list' is code-merge. In this case, the guy who merges 'change list' may not know enough about change. So, there should be information about checking patch result - to verify that patching is successful or not - as a comments of 'change list'.

One of good example is something like this.
* before patch : title string is "xxxx" in screen A.
* after patch : title stringis "yyyy" in screen B.

[[ blog 이사 과정에서 정확한 posting날짜가 분실됨. 년도와 분기 정도는 맞지 않을까? ]]

여러사람이 다양한 지역에서 같은 code branch를 가지고 작업할 경우, 특정인이 check-in한 code가 branch전체를 망치는 경우가 발생한다. build가 안될 수도 있고, program running초기에 비정상 동작으로 test자체가 불가능할 수도 있다. 한국과 영국 두 나라 개발자들이 같이 일을 하고 있는 상황을 생각해 보면, 한국인 개발자 누군가의 check-in 으로 인하여 branch가 전체가 망가지고 나서, 한국 사람들이 다 퇴근하면, 영국에서는 다음날 한국 사람이 출근할 때까지, 시차를 생각하면, 영국사람 입장에서는 working time내내, 아무일도 할 수 없게 된다. 물론, branch에 submit하는 개개인은 본인의 local pc에서 test한 후 정상 동작하는 것을 확인하고 branch에 check-in하겠지만, 개개인 local에 항상 최신 revision의 code를 가지고 있지는 않기 때문에 (항상 최신 revision의 code를 local에 유지하는 것은 상당한 overhead를 수반하기 때문에 이를 강제할 수는 없다. - 만약 이를 강제한다면, 여러사람이 일할 경우에는 sync받고 build하는 것 이외의 다른 일은 거의 할 수 없을 수도 있다.) 이전에 다른사람이 check-in한 code와 충돌해서 이런 현상이 벌어질 수도 있다.

그렇다면 이런 문제를 어떻게 해결할 수 있을까?
나는 이런 방법을 생각해 본다.

해당 branch에서 일하는 개발자들은 항상 자신이 일하는 office의 local time으로 오전, 9시~10시(출근하자마자) 사이에만 branch에 submit하도록 한다. 이렇게 하면, 그 날 하루동안 같은 time zone에 있는 여러 개발자들이 이 branch를 검증할 수 있는 시간이 생기고, branch에서 발생한 문제점을 check-in한 사람이 퇴근하기 전에 발견할 확률이 높아진다.

음.. 실제로 적용해 본적이 없으니... 효과는 잘...-_-;

[[ blog 이사 과정에서 정확한 posting날짜가 분실됨. 년도와 분기 정도는 맞지 않을까? ]]

* XP의 한계

저자가 이야기한 바와 같이 XP는 대규모 Project (여기서 말하는 대규모란, project를 하기 위해 필요한 사람 숫자가 많은 Project를 의미하는 것처럼 보인다 - 사람 숫자가 많다 = 필요한 Communication 양이 많다.)에는 적합하지 않은 것 같다. 이 점을 명확히 하고 XP를 적용하자.

* Test Oriented Development의 장점.
일단 Test Oriented로 하게 되면, Test를 위한 Interface를 가장 먼저 생각하게 된다. 즉 자연스럽게 "Interface Oriented Development"를 하게 된다. 또한 개발된 Program을 검증하기 용이할 확률이 높다. 중요한 것은 Auto Test란 것 자체가 논리적인 오류를 잡아낼 수는 있어도, User experience쪽에 부적합한 내용같은 부분은 잡아내는 것이 사실상 불가능 하다는 점이다. 또한 논리적인 오류 역시, 어느 정도 큰 Module을 대상으로 Auto Test를 돌리게 되면, 모든 경우를 Test하는 것이 상당히 어렵게 된다. 따라서 Auto Test는 작은 Unit Test에 적합한 것으로 보인다. (이렇게 하면, Test종류는 늘어나겠지만... )

* 고객의 역할.
고객은 business의 가치를 높이는 것이 무엇인지 파악하고, 무엇을 먼저 하고 무엇을 나중에 할 것인지를 결정하고, 시스템이 제대로 작동하는지 확인할 수 있는 test를 정의한다.

* 관리자의 역할.
관리자는 고객과 개발자를 한데 모으고 서로 융화시켜, 팀이 순조롭게 운영되도록 도와준다. 관리자는 process를 수행하는 것이 아니라, 단지 process가 원활하게 진행되도록 하는 것이다.
-> 훌륭한 관리자의 일은 처음부터 끝까지, 작업을 하고 있는 사람들 앞에 놓인 장애물을 치우는 일이다.

* XP 프로그램 작성.
- 코드의 공동 소유 : 누구든 현재 Project의 코드를 원하는 대로 수정할 수 있다.
- 단순한 설계 : 모든 test를 실행하고, 모든 idea를 표현하며, 최소한의 Class와 Method를 가지지만, 중복된 code를 포함하지 않는다.
- 지속적인 Refactoring: 기존 코드를 바꾸지 않는 이유는 잘못되는 것을 두려워해서이다. 따라서, 거의 100%가동되는 모든 단위 Test를 가지고 있다면 Refactoring은 두려워할 만한 일이 아니다.
- 지속적인 통합 : 지속적인 통합은 항상 releasable한 상태로 유지하고, 통합시에 나타날 수 있는 문제를 최대한 빨리 찾아낼 수 있다는 측면에서 중요하다. 통합이 늦어지면, debugging시 드는 비용은 늦어진 시간만큼 증가한다. 기하급수적으로...
- 코드작성표준
- 주당40시간 : 한 주 이상의 과도한 시간외 근무는, 코드의 질을 떨어뜨리며, bug를 양산하는 주범이다.

* 짝 Programming.
두 사람이 하나의 컴퓨터 앞에서 같이 Programming을 한다. "역동적인 2인조는 개별 Play를 하는 3명보다 낫다"
(=> 일단 XP에서는 이렇게 이야기하고 있는데... 난 여기에는 아직까진 동의할 수 없다. 이건 분명히 case by case..이다.!)
코드 공동 소유 vs. 코드의 건전성 유지.

[[ blog 이사 과정에서 정확한 posting날짜가 분실됨. 년도와 분기 정도는 맞지 않을까? ]]

XP(Extream programming)에 따르면, code branch는 항상 release가능한 상태를 유지해야 한다. 그런데 이는 XP에서 마찬가지로 주장하고 있는, "누구나 수정할 수 있고, 누구나 test가능한 code"의 개념과는 상반된다. 누구나 수정할 수 있고, 누구나 test가능하다면, 그 code branch는 언제든 깨어질 수 있다. 따라서, 항상 releasable할 수는 없다.

"누구나 수정할 수 있고, 누구나 test할 수 있다"는 것과 "코드는 항상 release가능한 상태를 유지해야 한다." 는 것을 어떻게 같이 가져갈 것인가????
나의 XP에 대한 지식이 짧아서 그런것 같은데...
(XP에서 어떻게 표현했는지, 그 정확한 영어 표현이 생각나지 않아서 한글로 적는다...)

[[ blog 이사 과정에서 정확한 posting날짜가 분실됨. 년도와 분기 정도는 맞지 않을까? ]]

"Design pattern" / "Extream programming" / "내 경험"

"Design pattern" 은 정말 유용하다. 이것은 코드를 좀더 flexible하고 또 건전하게 만든다. 그러나, software의 불변의 진리 중에 하나는, "Software를 soft하게 만들기 위해서는 비용이 든다" 이다. 즉 design pattern을 최대한 많이 적용해서, flexible한 코드를 만든다는 말은, 많은 추가적인 비용이 든다는 말이다. 또한 몇몇 책에서는, 개발자는 이후 변화할 요구사항에 대해 예측하는 것은 매우 어렵기 때문에, 초기부터 너무 flexible한 code를 짜고자 한다면, 이는 결국 project가 끝날 때까지 거의 쓸일이 없는, 쓸데없이 복잡한 구조를 만드는, over-engineering을 하게 된다고 주장한다("Addison Wesley Refactoring to patterns" 참조.). 따라서 Extream programming에서는 현재까지 알려진 요구사항만을 만족시킬 수 있는 최소한의 Flexibility를 가지는 software로 충분하며, 이후 추가되는 요구사항에 대해서는 refactoring을 통해서 계속해서 개선시켜 나갈 것을 권고하고 있다.

그런데, 문제는, 보통의 경우, 요구사항이 바뀌고, 이를 만족시키는 코드를 추가하고, 또 바뀌고, 추가하고... 이를 반복하다가 어느 순간, 이런 작업으로 인한 overhead가 너무 많다고 생각되면, source code refactoring을 하게 되며, 이는 source code의 구조를 바꾸는 큰 일이 된다. 즉, Extream programming에서 주장하듯 꾸준한 refactoring을 통해서, source code의 건전성을 확보하면 된다는 말은, 물론 좋은 말이긴 하다. 그러나 한편, 이는 design pattern을 이용한 "over-engineering"을 피하다가 결국 refactoring을 결정하기 까지, 많은 추가적인 비용을 들이게 되는, 또 다른 형태의 over-engineering을 하게 될 수도 있다..

결론적으로 절대적인 옳고 그름은 없다는 말을 하고 싶다.

개발자가 이후 발생할 요구사항의 변화를 100% 예측하는 것은 분명히 불가능하다. 그러나, 몇몇 부분에 대해서는 예측이 가능할 수도 있다. 그리고 이렇게 예측이 가능한 부분에 대해서도, 단지 현재의 요구사항에 없는 부분이라고 해서 flexible한 design을 적용하지 않는다면, over-engineering을 피하기 위해서, 또 다른 문제를 만들게 되는 폐단을 낳게 된다. (내가 보기에는 이것도 일종의 under-engineering이다. 따라서 이후 이것 또한 under-engineering으로 칭하기로 한다. - 이는 "Addison Wesley Refactoring to patterns" 에서 이야기하는 under-engineering과는 좀 다른 이야기이다.)

즉, flexible해야 한다고 강력하게 예측가능한 부분은 처음부터, 혹은 추가되는 요구사항을 구현하는 초기 부터 flexible하게 만드는 것이 under-engineering으로 발생하는 폐단을 막을 수 있다. 또한 programmer로서의 능력과 영감을 좌우하는 상당부분은 바로, 어떤 부분이 flexible해야 할 것인가? 를 판단하는 능력이 결정한다.

예를 하나 들어보자. (내가 경험한 실례를 가지고 설명한다.)

분산 컴파일을 위한 program을 만든다고 생각하자. 그리고 이 중에서도, network programming부분에 focus를 맞추어서 생각해 보자.
처음에 생각되는 요구사항은, 컴파일 할 파일을 전달하고, 결과로 만들어진 object binary file을 되돌려 받으면 될 것 같다.
그렇게해서, "size(4byte)+data(...)" 의 binary를 보내고 받는 socket function을 짠다. 그런데, programmer로서의 경험은, server-client간 통신은 항상 추가적인 필요가 발생했었고, 지금의 경우도 분명히 추가적인 무언가가 발생할 것이라고 말하고 있다. 그런 이유로, client-server가 network communication을 위한 layer를 만들었고, 단순히 compile을 위한 file과 object만이 아니라, 일반적인 data를 전송할 수 있게, protocol unit manager를 만들었다. 이후 이 두 module은 추가적인 요구사항이 발생할 때, 큰 어려움 없이 feature를 늘리는데 상당히 큰 도움을 주었다.

만약 만들지 않았다면?... 손발이 더욱 고생하지 않았을까?

+ Recent posts