왜 자바에서 static의 사용을 지양해야 하는가?

자바에서 데이터를 가공하거나 특정 메서드를 수행할 때 새로운 클래스를 만들어서 이를 인스턴스화 해서 쓸건지 아니면 static 으로 쓸건지 고민하게 될 때가 있다. 사실 후자는 객체지향적 관점에서 그리 좋은 선택은 아니다. Vamsi Emani라는 프로그래머가 stack overflow에 남긴 질문 Why are static variables considered evil? 과 가장 많은 지지를 받은 두개의 답변을 번역했다.


Q by V. Emani

I am a Java programmer who is new to the corporate world. Recently I’ve developed an application using Groovy and Java. All through the code I’ve used quite a good number of statics. I was asked by the senior technical lot to cut down on the number of statics used. I’ve googled about the same, and I find that many programmers are fairly against using static variables.

저는 현업에 갓 뛰어든 자바 프로그래머입니다. 근래에 Groovy와 Java를 이용하는 어플리케이션을 개발하고 있습니다. 그동안 자바로 개발할 때 "static" 변수(그리고 static 메소드)를 꽤나 많이 이용하는 습관을 가지고 있었습니다. 근데 제 시니어는 static 의 개수를 줄이라고 말합니다. 그 이유가 궁금해서 구글에 검색을 해봤는데 많은 자바 프로그래머가 static 을 사용하는 것을 꺼린다는 것을 발견했습니다.

I find static variables more convenient to use. And I presume that they are efficient too (please correct me if I am wrong), because if I had to make 10,000 calls to a function within a class, I would be glad to make the method static and use a straightforward class.methodCall() on it instead of cluttering the memory with 10,000 instances of the class, right?

사실 저는 static 을 이용하는 것이 보다 편하고 효율적이라고 생각합니다. (제가 틀린 부분이 있으면 지적해주세요!) 어떤 클래스 내에서 만번의 함수 호출을 하게 된다면 그 함수를 static으로 만들어서 class.methodCall()의 형태로 사용하는 것이 만개의 인스턴스를 생성해서 메모리를 어지럽히는 것보다 훨씬 낫지 않을까요?

Moreover statics reduce the inter-dependencies on the other parts of the code. They can act as perfect state holders. Adding to this I find that statics are widely implemented in some languages like Smalltalk and Scala. So why is this oppression for statics prevalent among programmers (especially in the world of Java)?

PS: please do correct me if my assumptions about statics are wrong.

게다가 static 변수는 코드의 상호의존성(inter-dependency)을 줄여준다고 생각합니다. 상태를 저장하는데 있어서 static 변수들은 아주 유용하게 사용될 수 있습니다. 사실 static은 자바 뿐만 아니라 Smalltalk나 Scala 와 같은 프로그래밍 언어에서도 널리 이용되고 있습니다. 근데 왜 유독 자바 프로그래밍 세계에선 개발자들이 static의 사용을 꺼리는 걸까요?

PS: static 변수에 대한 제 생각에 틀린 부분을 지적해주시면 감사하겠습니다.


A by J. Skeet

Static variables represent global state. That’s hard to reason about and hard to test: if I create a new instance of an object, I can reason about its new state within tests. If I use code which is using static variables, it could be in any state – and anything could be modifying it.

Static 변수는 global state(역주: 전역 상태. 프로그램 혹은 한 모듈 전체의 상태)를 상징합니다. Global state는 추론과 테스트가 매우 까다롭습니다. 가령 코드에서 static 변수를 사용한다고 하면, 이 변수의 상태는 코드 여러 부분에서 영향을 받을 수 있게 되고 따라서 변화를 추적하기가 어려워집니다. 반면에 이를 객체화하여 인스턴스로 생성하면 테스트 중에 그 변수가 어떤 상태를 가지고 있는지 추론하는 것이 보다 간단해집니다.

I could go on for quite a while, but the bigger concept to think about is that the tighter the scope of something, the easier it is to reason about. We’re good at thinking about small things, but it’s hard to reason about the state of a million line system if there’s no modularity. This applies to all sorts of things, by the way – not just static variables.

프로그래머로서 제가 그동안 경험해온 바에 따르면 큰 개념에 대해서 그리기 위해선 일단 이해하고자 하는 범위를 좁혀 쉽게 추론할 수 있어야 합니다. 일반적으로 우리는 작으면 작을수록 그 대상을 쉽게 이해합니다. 다시 말해, 모듈화를 제대로 하지 않는다면 백만 줄 짜리 시스템의 상태에 대해서 추론하는 것은 굉장히 어려운 일입니다. 이것은 단순히 static 변수 뿐만 아니라 모든 프로그래밍 이슈에 대해서 적용할 수 있는 중요한 사실입니다.


A by A. Lockwood & J. Brown

Its not very object oriented: One reason statics might be considered "evil" by some people is they are contrary the object-oriented paradigm. In particular, it violates the principle that data is encapsulated in objects (that can be extended, information hiding, etc). Statics, in the way you are describing using them, are essentially to use them as a global variable to avoid dealing with issues like scope. However, global variables is one of the defining characteristics of procedural or imperative programming paradigm, not a characteristic of "good" object oriented code. This is not to say the procedural paradigm is bad, but I get the impression your supervisor expects you to be writing "good object oriented code" and you’re really wanting to write "good procedural code".

첫째로, static은 객체 지향적이지 않습니다: 개발자들이 static 변수를 ‘악’으로 규정하는 이유는 static 변수가 객체 지향의 패러다임과 상반되기 때문입니다. 특히나 static 변수는, 각 객체의 데이터들이 캡슐화되어야 한다는 객체지향 프로그래밍의 원칙(역주: 한 객체가 가지고 있는 데이터들은 외부에서 함부로 접근하여 수정할 수 없도록 해야 한다는 원칙)에 위반됩니다. 질문자께서 스스로 설명했듯이 static은 스코프(역주: 한 변수가 유효한 범위)를 고려할 필요가 없는 경우, 즉 전역 변수를 사용할 때에 유용합니다. 이는 절차지향적 프로그래밍 혹은 명령형 프로그래밍(역주: C가 대표적인 절차지향적, 명령형 프로그래밍 언어이며 Java 역시 큰 범위에서 절차지향적, 명령형 프로그래밍 언어라고 할 수 있다.)에서 매우 중요한 개념입니다. 하지만 이 것이 객체지향의 관점에서 좋은 코드라고 얘기하기는 힘듭니다. 절차지향 패러다임이 나쁘다는 것이 아닙니다. 다만, 당신의 시니어는 당신이 "객체지향적으로 좋은 코드"를 짜기를 바라는 것입니다. 반대로 당신은 "절차지향적으로 좋은 코드"를 짜기를 원하는 것이라고 말할 수 있을 것입니다.

There are many gotchyas in Java when you start using statics that are not always immediately obvious. For example, if you have two copies of your program running in the same VM, will they shre the static variable’s value and mess with the state of each other? Or what happens when you extend the class, can you override the static member? Is your VM running out of memory because you have insane numbers of statics and that memory cannot be reclaimed for other needed instance objects?

사실 자바에서 static을 사용하기 시작하면 예측이 어려운 문제가 많아지게 됩니다. 예를 들어서 하나의 가상머신에서 어떤 프로그램 두 카피가 돌고 있다고 가정해봅시다. 만약 이 두 카피가 동일한 static 변수를 공유하게 된다면, 서로의 상태에 영향을 주게 되지 않을까요? 더불어서 오버라이딩을 할 수 없는 static 멤버들 때문에 클래스를 확장하는게 어려워질 것입니다. 뿐만 아니라 지나치게 많은 static 변수를 사용하게 되면 이들로부터 메모리 회수를 할 수 없어서 가상머신이 메모리 부족을 겪게 될 것입니다.

Object Lifetime: Additionally, statics have a lifetime that matches the entire runtime of the program. This means, even once you’re done using your class, the memory from all those static variables cannot be garbage collected. If, for example, instead, you made your variables non-static, and in your main() function you made a single instance of your class, and then asked your class to execute a particular function 10,000 times, once those 10,000 calls were done, and you delete your references to the single instance, all your static variables could be garbage collected and reused.

객체의 라이프타임: 추가로, static 변수는 프로그램이 실행되고 있는 내내 살아있게 됩니다. 즉, 그 클래스를 이용한 작업을 끝내더라도 static 변수가 점유하고 있는 메모리는 garbage collector(역주: 사용하지 않는 메모리를 회수하는 기능)에 의해서 회수되지 않게 됩니다. 반대로, 프로그래머가 그 변수를 인스턴스화 해서 main() 함수 내에서 하나의 인스턴스로 생성하게 되면, 그리고 그 인스턴스에게 만번의 함수 호출을 시키게 되면 그 만번의 함수 호출이 끝난 후 인스턴스는 소멸됩니다. 따라서 메모리를 훨씬 절약할 수 있게 됩니다.

Prevents certain re-use: Also, static methods cannot be used to implement an interface, so static methods can prevent certain object oriented features from being usable.

static은 재사용성이 떨어집니다: 또한, static 메서드는 interface를 구현하는데 사용될 수 없습니다. 즉 static 메서드는 프로그래머가 (재사용성을 높여주는)이러한 자바의 유용한 객체지향적 기능들을 사용하는 것을 방해합니다.

Other Options: If efficiency is your primary concern, there might be other better ways to solve the speed problem than considering only the advantage of invocation being usually faster than creation. Consider whether the transient or volatile modifiers are needed anywhere. To preserve the ability to be inlined, a method could be marked as final instead of static. Method parameters and other variables can be marked final to permit certain compiler optimizations based on assumptions about what can change those variables. An instance object could be reused multiple times rather than creating a new instance each time. There may be complier optimization switches that should be turned on for the app in general. Perhaps, the design should be set up so that the 10,000 runs can be multi-threaded and take advantage of multi-processor cores. If portability isn’t a concern, maybe a native method would get you better speed than your statics do.

static의 대안들: 프로그래머에게 효율(여기서는 속도)이 가장 중요한 문제여서 객체를 생성할 때 마다 생기는 사소한 불이익에도 민감한 상황일 수 있습니다. 이 경우에도 여전히 static 대신에 다른 방법들을 사용하는 것이 가능합니다. 먼저 "transient"나 "volatile"과 같은 제어자(modifier)를 쓸 수 있는지 먼저 고려해봅니다. 실행 속도를 빠르게 해주는 메소드 인라이닝(역주: 실제 메소드를 호출하지 않고 바로 결과값을 돌려주는 방식)을 위해 "final" 메서드를 사용하는 것도 생각해볼 수 있습니다. 또한 메서드 파라미터들과 변수들이 final로 선언되면 컴파일러 단에서의 최적화 작업이 가능해집니다. 인스턴스를 사용할 때마다 새로 생성하는 대신에 여러번 재사용할 수도 있습니다. 아마도 컴파일러 단의 최적화 작업이 switches that should be turned on for the app in general. 어쩌면 멀티스레드를 이용해서 멀티코어 프로세스의 장점을 극대화하기 위해선 이런 디자인이 필수적일 수도 있습니다. 이식성(역주: 다른 플랫폼으로 쉽게 옮길 수 있는 특성)이 중요한 것이 아니라면, native 메서드를 사용해서 static을 사용하는 것보다 더 빠르게 만들 수도 있을 것입니다.

If for some reason you do not want multiple copies of an object, the singleton design pattern, has advantages over static objects, such as thread-safety (presuming your singleton is coded well), permitting lazy-initialization, guaranteeing the object has been properly initialized when it is used, sub-classing, advantages in testing and refactoring your code, not to mention, if at some point you change your mind about only wanting one instance of an object it is MUCH easier to remove the code to prevent duplicate instances than it is to refactor all your static variable code to use instance variables. I’ve had to do that before, its not fun, and you end up having to edit a lot more classes, which increases your risk of introducing new bugs…so much better to set things up "right" the first time, even if it seems like it has its disadvantages. For me, the re-work required should you decide down the road you need multiple copies of something is probably one of most compelling reasons to use statics as infrequently as possible. And thus I would also disagree with your statement that statics reduce inter-dependencies, I think you will end up with code that is more coupled if you have lots of statics that can be directly accessed, rather than an object that "knows how to do something" on itself.

만약 여러개의 인스턴스를 만드는 것을 피하고 싶다면 싱글톤 디자인 패턴을 이용하는 것이 훌륭한 대안이 될 수 있습니다. 싱글톤 디자인은 (싱글톤을 제대로 구현했다는 전제하에) 스레드 안정성을 가지고, lazy-initialization(역주: 객체가 필요할 때마다 만들어 쓰는 기법)을 허용하며, 객체가 사용될 때마다 제대로 초기화 된다는 것을 보장합니다. 뿐만 아니라 서브 클래싱(sub-classing) 기법을 가능하게 하고, 테스트와 리팩토링이 매우 용이합니다. 다음의 상황을 가정해봅시다. 프로그래밍을 하다가 어느 시점에서 지금까지의 설계를 바꿔야겠다는 생각이 들게 되면 두말할 것도 없이 하나의 인스턴스를 수정하는 것이 모든 static 변수들을 리팩토링 하는 것보다 훨씬 편할 것입니다. 사실 static을 사용하다가 refactoring을 해야하는 상황은 매우 흔한 일입니다. 그것은 유쾌하지 않은 일일 뿐 아니라 훨씬 많은 클래스를 수정하게 만들기도 합니다. 이렇게 또다시 클래스들을 수정하다보면 새로운 버그를 만들어낼 소지가 매우 커집니다. 이런 상황을 피하기 위해서 처음에 "제대로"(위에서 언급한 방식들대로) 디자인하여 코딩하는 것이, 그 방식이 몇가지 단점을 가지고 있는 것 처럼 보여도 훨씬 나은 선택입니다. 사실 이런 끔찍한 재수정 작업이 요구될지도 모른다는 소지가 제가 static을 되도록 쓰지 않으려는 가장 큰 이유 중 하나입니다. 정리하자면, 저는 질문자께서 static이 코드의 상호의존성(inter-dependency)을 줄여준다고 말하신 것에 동의할 수 없습니다. 인스턴스화 되어 있는 객체들을 쓰지 않고 static 변수에 직접 접근하는 방식으로 코드를 짜다보면, 결국 작성한 모듈들이 서로 더 많이 엮이는 (바람직하지 않은) 상황에 처하게 될 것입니다.

Paul Graham의 Programming Bottom-Up

Viaweb(야후에 인수됨)과 Y Combinator의 창업자이자 Lisp의 선구자인 Paul Graham은 자신의 웹사이트 paulgraham.com에 프로그래밍과 스타트업에 대해 훌륭한 에세이들을 기고해왔다. Programming Bottom-Up은 그가 bottom-up 프로그래밍 방식의 장점에 대해서 강조하기 위해 쓴 에세이이다. 아래에 에세이의 전문 번역을 포스팅한다.


This essay is from the introduction to On Lisp. The red text explains the origins of Arc’s name.

이 에세이는 Paul Graham의 저서 On Lisp: : Advanced Techniques for Common Lisp (1993) 의 서문에 실린 글입니다. Paul Graham은 On Lisp에서 프로그래밍 언어 Lisp 을 이용한 프로그래밍 기법과 bottom-up 프로그래밍에 대해 저술하였습니다.

It’s a long-standing principle of programming style that the functional elements of a program should not be too large. If some component of a program grows beyond the stage where it’s readily comprehensible, it becomes a mass of complexity which conceals errors as easily as a big city conceals fugitives. Such software will be hard to read, hard to test, and hard to debug.

프로그래밍의 세계에서, 기능별로 분리된 하나의 프로그램 컴포넌트를 지나치게 크게 설계하면 안된다는 것은 아주 오래된 불문율입니다. 어떤 프로그램 컴포넌트가 지나치게 커져 한번에 이해할 수 있는 규모를 넘어가게 되면 에러들을 찾는 것이 매우 어려워집니다. 마치 영화 속 악당들이나 범죄자들이 거대한 도시에 숨어 감시와 추적을 피하는 것과 같은 원리인 것입니다. 하나의 컴포넌트가 너무 커지면, 소프트웨어의 가독성이 떨어질 뿐만 아니라 테스트와 디버깅을 하기가 매우 어려워집니다.

In accordance with this principle, a large program must be divided into pieces, and the larger the program, the more it must be divided. How do you divide a program? The traditional approach is called top-down design: you say “the purpose of the program is to do these seven things, so I divide it into seven major subroutines. The first subroutine has to do these four things, so it in turn will have four of its own subroutines,” and so on. This process continues until the whole program has the right level of granularity– each part large enough to do something substantial, but small enough to be understood as a single unit.

이런 사실에 입각하여 프로그래머들은 일반적으로 하나의 큰 프로그램을 여러개의 조각들로 나눕니다. 큰 프로그램일 수록 당연히 더 많은 조각들로 나뉘어집니다. 이런 전통적인 프로그램 설계 방식은 흔히 top-down 디자인이라고 불립니다. 예를 들자면 “만약 전체 프로그램이 일곱개의 기능을 가져야 한다고 하면, 프로그램을 일곱개의 서브루틴으로 나눌 수 있고, 첫번째 서브루틴은 다시 4개의 하위 기능을 가져야 하므로 네개의 하위 서브루틴으로 나눌 수 있고…” 이런 과정이 반복되는 방식 입니다. 이렇게 프로그램을 쪼개는 과정은 전체 프로그램이 충분히 잘게 쪼게질때까지 반복되는데 나뉘어진 각 부분은 충분히 구현되어야 하는 기능을 충실히 수행하면서도 하나의 단위로서 충분히 쉽게 이해될 수 있어야 합니다.

Experienced Lisp programmers divide up their programs differently. As well as top-down design, they follow a principle which could be called bottom-up design– changing the language to suit the problem. In Lisp, you don’t just write your program down toward the language, you also build the language up toward your program. As you’re writing a program you may think “I wish Lisp had such-and-such an operator.” So you go and write it. Afterward you realize that using the new operator would simplify the design of another part of the program, and so on. Language and program evolve together. Like the border between two warring states, the boundary between language and program is drawn and redrawn, until eventually it comes to rest along the mountains and rivers, the natural frontiers of your problem. In the end your program will look as if the language had been designed for it. And when language and program fit one another well, you end up with code which is clear, small, and efficient.

하지만 숙련된 Lisp 프로그래머들은 top-down 디자인 만큼이나 bottom-up 디자인이라고 불리는 방식을 자주 이용합니다. 이 디자인 원칙의 핵심은, 한마디로 요약하면 이용하는 프로그래밍 언어를 개발하려는 프로그램에 최적화되도록 만들어 나간다는 것에 있습니다. 즉, Lisp을 이용해서 프로그램을 작성할 때 프로그램을 쪼개어 나가면서 (위에서 아래로 내려간다는 표현을 씁시다.) 가장 하위의 언어의 단계로까지 내려가게 하는 것이 아니라, 동시에 언어 자체를 확장해나가며 프로그램의 단계로 올라가게 한다는 것입니다. 만약 프로그램의 어떤 부분을 작성하면서 “나는 Lisp이 이런 연산자를 가지고 있으면 좋을 것 같다.”라고 생각하면 그 기능을 바로 구현하기 시작하는 것입니다. 후에 개발을 진행하면서 그 새로운 연산자가 다른 부분에서도 유용하게 쓰여 프로그램의 설계를 훨씬 단순화시킬 수 있다는 것을 발견할 수 있을 것입니다. 좀 더 쉽게 말해서, bottom-up 방식에서는 프로그램과 프로그래밍 언어가 동시에 진화합니다. 마치 전쟁 중인 두 국가의 국경선이 끊임없이 다시 그려지다가 마침내 산과 강을 따라서 수렴하게 되는 것처럼, 언어와 프로그램 사이의 경계선도 끊임없이 다시 그어지다가 프로그램에 최적화되어 있는 경계선으로 확정되게 됩니다. 이렇게 완성된 프로그램에선 마치 프로그램 언어가 처음부터 이 프로그램을 위해서 디자인되어 있었던 것처럼 보이게 됩니다. 프로그램과 프로그래밍 언어가 서로에게 가장 알맞는 모습이 되면, 프로그래머가 짠 코드는 굉장히 명료하고, 간단하며 효율적인 형태가 되어 있을 것입니다.

It’s worth emphasizing that bottom-up design doesn’t mean just writing the same program in a different order. When you work bottom-up, you usually end up with a different program. Instead of a single, monolithic program, you will get a larger language with more abstract operators, and a smaller program written in it. Instead of a lintel, you’ll get an arch.

여기서 중요한 것은 bottom-up 디자인이 단지 프로그램을 작성할때 top-down 방식으로 프로그램을 작성할때의 반대 순서로 작성하는 것만을 의미하는 것은 아니라는 점입니다. 프로그래머가 bottom-up 방식으로 프로그램을 작성하면, 완성된 프로그램은 top-down 방식으로 작성한 프로그램과는 전혀 다른 모습이 됩니다. 하나의 통일된 프로그램 대신에 좀 더 추상화된 연산자들을 가지게 되며, 훨씬 더 간결한 프로그램으로 재탄생하게 됩니다. 건축에 비유하자면 천장을 지탱하기 위해서 두개의 기둥을 쌓아올리고 그 위에 대들보(lintel)를 올려놓는 대신에 처음부터 아치(arch)를 쌓아올리는 것으로 표현 할 수 있을 것입니다. (역주: 참고로 이러한 Paul Graham의 아치 비유는 Paul Graham과 Robert Morris가 개발한 Lisp의 방언인 Arc의 이름의 기원이 되었습니다.)

In typical code, once you abstract out the parts which are merely bookkeeping, what’s left is much shorter; the higher you build up the language, the less distance you will have to travel from the top down to it. This brings several advantages:

일단 프로그래머가 필요하다고 생각하는 부품들을 추상화하여 쌓아올리고 나면, 남은 작업은 매우 간단합니다. 프로그래밍 언어를 더 높이 쌓아올릴 수록, top-down을 해야 하는 거리가 줄어들게 되므로 따라서 작업이 훨씬 간단해지는 것입니다. 이는 다음의 몇가지 장점들을 불러옵니다.

  1. By making the language do more of the work, bottom-up design yields programs which are smaller and more agile. A shorter program doesn’t have to be divided into so many components, and fewer components means programs which are easier to read or modify. Fewer components also means fewer connections between components, and thus less chance for errors there. As industrial designers strive to reduce the number of moving parts in a machine, experienced Lisp programmers use bottom-up design to reduce the size and complexity of their programs.
  2. Bottom-up design promotes code re-use. When you write two or more programs, many of the utilities you wrote for the first program will also be useful in the succeeding ones. Once you’ve acquired a large substrate of utilities, writing a new program can take only a fraction of the effort it would require if you had to start with raw Lisp.
  3. Bottom-up design makes programs easier to read. An instance of this type of abstraction asks the reader to understand a general-purpose operator; an instance of functional abstraction asks the reader to understand a special-purpose subroutine.
  4. Because it causes you always to be on the lookout for patterns in your code, working bottom-up helps to clarify your ideas about the design of your program. If two distant components of a program are similar in form, you’ll be led to notice the similarity and perhaps to redesign the program in a simpler way.
  1. 프로그래밍 언어의 기능이 확장됨으로써, 프로그램의 구현은 훨씬 간결하고(짧고) 또 훨씬 유연해집니다. 간결하고 짧은 프로그램은 여러개의 컴포넌트로 나뉘어질 필요가 없습니다. 이는 코드를 읽고 고치기가 훨씬 더 자유로워진다는 것을 의미합니다. 또한 컴포넌트의 개수가 줄어든다는 것은 컴포넌트 간의 연결이 훨씬 줄어든다는 것을 의미하기도 합니다. 따라서 에러에 가장 취약할 수 있는 이 연결 고리들 사이에서 에러가 발생할 가능성을 줄이게 됩니다. 기계를 설계하는 산업 디자이너나 엔지니어들이 움직이는 부속의 수를 줄이기 위해서 고군분투하는 것과 마찬가지로, 숙련된 Lisp 프로그래머들은 bottom-up 디자인을 이용해서 프로그램의 크기와 복잡도를 줄이는데 몰두합니다.
  2. Bottom-up 디자인은 코드의 재활용성(reusability)을 증대시킵니다. 흔히, 여러개의 프로그램을 작성할때 처음 프로그래머가 작성했던 유틸리티(역주: 앞에서 프로그래머가 언어 레벨에서 필요하다고 생각했던 기능을 Bottom-up 방식으로 구현한 구현체들)들은 그 다음 프로그램들을 작성할 때 요긴하게 사용됩니다. 경험 많은 프로그래머가 이전의 프로그램들을 위해서 충분히 이런 유틸리티들을 많이 구현해놓고 있다면 raw Lisp (이런 유틸리티들이 전혀 구현되어 있지 않은 순수한 Lisp)으로 처음부터 프로그램을 작성하는 것보다 훨씬 적은 노력으로 프로그램을 작성할 수 있게 됩니다.
  3. (프로그래머가 프로그래밍 언어를 확장하기 위해서 짠 유틸리티에 대해 이해하고 있다는 전제 하에) Bottom-up 디자인은 프로그램의 가독성을 높여줍니다. Bottom-up 디자인과 같은 추상화 방식에서는 연산자들을 좀 더 일반적으로 사용하는 것이 가능합니다. 반면에 함수를 이용한 추상화 방식에서는 프로그램을 읽는 사람이, 프로그래머가 정의한 이 새로운 함수를 이해할 것을 요구합니다. (역주: 예를 들어 "Hello"라는 문자열과 "World"라는 문자열을 잇는 작업을 한다고 했을 때 Bottom-up 디자인에서는 "Hello" + "World" 와 같이 일반적인 + 연산자를 사용할 수 있게 언어를 확장하는 것이 가능합니다. Bottom-up 방식을 사용하지 않고 함수로 이를 구현하는 경우에는 이 작업을 해주는 함수, 가령 concat("Hello", "World") 와 같은 함수를 만들고 호출하는 식으로 구현이 됩니다. 전자가 후자보다 가독성이 낫다는 것은 다소 자명한 것입니다.)
  4. 마지막으로, Bottom-up으로 프로그램을 작성하기 위해선 전체 코드의 패턴을 살피고 이해하고 있어야 하므로, 프로그래머가 작성하는 프로그램의 디자인에 대한 아이디어를 명확히 이해하는데 도움을 줍니다. 동일한 패턴을 가지고 있는 두개의 컴포넌트가 꼭 코드 상에서 붙어있으리라는 보장은 없습니다. 만약 멀리 떨어져있는 두개의 컴포넌트가 유사성을 가지고 있다면 프로그래머는 그 유사성을 파악하고, 전체 프로그램을 다시 좀 더 간단한 형태로 설계할 수 있도록 유도될 것입니다.

Bottom-up design is possible to a certain degree in languages other than Lisp. Whenever you see library functions, bottom-up design is happening. However, Lisp gives you much broader powers in this department, and augmenting the language plays a proportionately larger role in Lisp style– so much so that Lisp is not just a different language, but a whole different way of programming.

Bottom-up 디자인은 Lisp 말고도 다른 언어들에도 어느정도 적용될 수 있습니다. 라이브러리 함수들은 bottom-up 디자인의 대표적인 예라고 볼 수 있을 것입니다. 물론 Lisp에서 bottom-up 디자인은 훨씬 더 빛을 발합니다. 이 것은 Lisp의 스타일이 단지 C와 Java와 같은 프로그램과 다른 문법을 가진 프로그래밍 언어라서가 아니라, 완전히 다른 방식의 프로그래밍 패러다임이기 때문입니다. (Lisp의 함수형 프로그래밍으로서의 특성에서 기인합니다.)

It’s true that this style of development is better suited to programs which can be written by small groups. However, at the same time, it extends the limits of what can be done by a small group. In The Mythical Man-Month, Frederick Brooks proposed that the productivity of a group of programmers does not grow linearly with its size. As the size of the group increases, the productivity of individual programmers goes down. The experience of Lisp programming suggests a more cheerful way to phrase this law: as the size of the group decreases, the productivity of individual programmers goes up. A small group wins, relatively speaking, simply because it’s smaller. When a small group also takes advantage of the techniques that Lisp makes possible, it can win outright.

사실 이런 소프트웨어 개발 스타일은 큰 규모의 팀보다는 작은 규모의 팀에서 작업할 때 더 적합합니다. 대신 이런 방식은 작은 규모의 팀이 할 수 있는 일의 한계를 놀라울 정도로 증대시킵니다. Frederick Brooks는 그의 저서 Mythical Man-Month에서 프로그래머 그룹의 생산성은 팀의 규모와 정비례하지 않는다는 유명한 이론을 제안한 바 있습니다. 실제로 많은 경우에, 팀의 규모가 커질수록 프로그래머 개개인의 생산성은 떨어지게 됩니다. 반대로 팀의 규모가 줄어들수록 프로그래머 개개인의 생산성은 향상됩니다. 작은 팀이 큰 팀에 승리하는 이유는 단지 규모가 작기 때문입니다. 그러나 만약에 작은 규모의 팀이 Lisp 프로그래밍이 가능하게 만든 bottom-up 프로그래밍의 장점을 취할 수 있게 된다면 어떤 일이 일어날까요? 그 팀은 천하무적의 팀이 될 것입니다.

Paul Graham은 늘 전서계의 개발자들에게 자신이 믿고 있는 프로그래밍 방법론들과 패러다임의 장점에 대해 설파하는데 열심이다. 이 글에서도 새로운 신도들을 만드려는 듯한 그의 의지가 여전하다.

이 글을 온전히 이해하기 위해서는 함수형 프로그래밍 패러다임에 대한 깊이 있는 이해가 필요한 것처럼 보이지만 일단 그 방대한 내용들을 공부하기 위해서는 많은 시간이 걸릴 것이므로 일단 논외로 치려고 한다. 그러나 bottom-up 이라는 용어를 처음들어본 사람들에게도 익숙할 사용자 정의 라이브러리나 API와 같은 개념을 ‘언어의 확장’이라는 측면에서 접근했다는 점에서 매우 신선하다는 생각이 들었다. ‘마치 그 프로그램을 위해서 존재하는 언어를 만들 듯이’ 프로그래밍 한다는 그의 표현은, 분명 이런 설계 기법이 생소한 사람들에게도 단박에 와닿는 설명이다. 더불어서 왜 이 기법이 top-down 방식에 비해서 이점을 가지는지에 대한 설명도 너무나 논리적이고 탁월하다.

마치 기계를 설계할 때 표준화된 부품들을 사용함으로서 그 종류의 개수를 줄이려는 시도를 하려는 것처럼 프로그래밍에서도 처음부터 이런 ‘표준화된 부품(본문의 컴포넌트 혹은 유틸리티)’들을 미리 만들어 놓음으로서 효율적인 구현을 하겠다는 것은, 비록 이를 염두에 두고 작업을 해보지는 않았지만 분명 고개를 끄덕일 수 있을만한 합당한 시도처럼 보인다.

다만 그가 지적하는 것처럼, 그 ‘표준화된 부품’을 만들기 위해선, 어떤 부품을 표준화 해야 할 것인가에 대한 프로그래머의 통찰력이 분명 필요할 것이며 그 것은 경험이 많은 프로그래머들 만이 익숙하게 해낼 수 있는 작업일 것이다. 그러나 그의 주장을 염두에 두고 프로그래밍 하면 분명 효율 면에서 큰 개선을 이룰 수 있을 것 같다.

저자의 주장의 유효성에 대해서 논하는 것을 떠나서, 늘 테크 구루들이나 뛰어난 엔지니어들의 글에선, 사소하게 지나갈 수 있는, 너무나도 당연해 보이는 접근이나 방식들을, 그들은 얼마나 잘 체계화하고 이론으로 만들어 방법론화하는지 실감하게 된다. 이런 패러다임화를 통해서 탄탄한 시스템을 구축하고 좀 더 효율적인 작업을 꾀하는 이들의 노력이, 수준 높은 차원의 엔지니어링에는 필수적인 요소라는 것을 다시 한번 느끼게 된다.

Managing Vim plugins using Vundle

Vim 에디터는 정말 강력한 개발도구이다. 처음 Vim을 사용하는 사람들에게는 어떻게 사용해야하는지 난감한 툴이지만 조금만 손에 익으면 다른 텍스트 에디터에서는 사용할 수 없는 고급 기능들을 사용해서 빠르게 코드를 작성할 수 있게 된다. 이렇게 Vim 이 강력한 이유는 무엇보다도 커스터마이징이 자유롭고 다양한 plugin 들을 사용할 수 있기 때문일 것이다. 물론 최근에 sublime text나 github의 atom 같은 텍스트 에디터가 멋진 plugin 들을 많이 제공하고 있지만 GUI(Graphic User Interface) 환경이 아닌 CLI(Command Line Interface)에서는 사용할 수 없다는 단점이 있다. 최근 Vim의 plugin을 관리해주는 다양한 툴들이 많이 개발되고 있다. 나는 이 중에서 Vundle을 자주 이용하는 편이다. 이 포스팅에서는 Vundle을 이용하는 법에 대해서 예제와 함께 간단히 작성해보려고 한다.


Install Vundle

  • 먼저 git clone 명령으로 ~/.vim/bundle 경로에 Vundle repository를 복사해놓는다. (물론 git이 설치되어 있어야 한다.)

  • 그 다음에는 vimrc 파일을 수정해줄 차례다. vim ~./vimrc 커맨드로 vimrc 파일을 vim으로 열어준다.
  • 아래는 vundle git repository 에 올라와있는 튜토리얼이다. 일단은 먼저 아래 내용을 살펴보자.

먼저 Vundle을 잡아주고 그 밑에 Vundle로 설치할 Plugin들을 리스팅 해주고 있다. 번들도 Plugin의 일종이기 때문에 맨 처음에

Plugin 'VundleVim/Vundle.vim' 로 Vundle을 기입해준다. 이 예제에서는 그 밑에 Fugitive, L9 등의 Plugin을 리스팅하고 있는 것을 볼 수 있다. 우선은 Vundle만 먼저 설치해주자.

  • vimrc 파일을 저장하고 나서 vim 커맨드 모드에서 :PluginInstall을 입력한다.

image1
* Vundle Plugin이 설치되면 Done! 이라고 표시된다. (시간이 조금 걸릴 수도 있다.)


Plugin Install

  • 이제 본격적으로 Plugin들을 설치해보자. 앞에서 얼추 살펴보았듯이 설치할 Plugin들을 vimrc 파일에 리스팅 해주고 (그리고 나서 당연히 수정한 vimrc 파일을 :w 커맨드로 저장해주어야 한다. 위에서 처럼 :PluginInstall 를 입력해주면 Vundle이 알아서 Plugin들을 설치해준다. (참 편리하다!)

  • 설치할 Plugin들은 Vim Awesome 과 같은 웹에서 찾을 수 있다.

image2
* 필자의 vimrc 파일은 다음과 같다.

Function Tracker, Nerd Tree, vim-airline, goldenvie, gitgutter 등의 Plugin과 Molokai 테마를 설치하여 사용하고 있다.

  • 설치할 Plugin들을 리스팅 한 다음에는 vimrc 파일을 저장하고 나서 vim 커맨드 모드에서 :PluginInstall 을 입력한다.

  • 설치가 완료되었다. 이제 설치되었는지 확인하기 위해서 vim을 열고 Plugin을 활성화 하는 커맨드를 입력한다. 예를 들어서 NERDTree의 경우에는 :NERDTreeToggle 을 입력해주어야 한다. 다음과 같이 나타나면 NERDTree 플러그인이 활성화 된 것이다.

image3
* 팁으로 만약 NERD Tree를 이용할 때 항상 vim을 열었을때 기본으로 NERD Tree가 뜨도록 만들고 싶으면 필자의 vimrc에서 처럼 맨 밑에 아래 코드를 추가해준다.

min() / max() problems in C++

항상 C++로 코딩을 하면서 min()이나 max() 함수(최소값/최대값을 리턴하는 함수)를 쓸때 이상한 에러를 맞닥뜨리곤 한다.

나중에 안 사실이지만 이 것은 min()/max() 라는 이름으로 선언된 함수들끼리 서로 충돌을 일으키기 때문이다. 즉 include 한 header 파일 내에 동일한 이름으로 다른 함수들이 선언되어 있는 것이다.

몇가지 해결책을 이 블로그에서 찾았다. 해결책은 아래와 같다.


I always confronted something weird errors when I use min() or max() function in C++.

Actually, it because of conflicts between the functions with same name.

I found several solutions for this problems from this blog post.

And solutions suggested are following. (prioritized with best approach first):


이하 내용은 블로그에서 인용.

Following suggestions are cited from the blog.

  • Convert your source code to use the Standard C++ std::min() and std::max() and ensure that you #include . Use

  • Define the NOMINMAX macro to instruct WinDef.h not to define the min/max macros. E.g. you can update your Visual C++ compiler options with /D NOMINMAX or you can insert #define NOMINMAX.

  • Redefine min/max in the specific problematic files. Especially this may be needed in you include gdiplus Windows headers files, because these uses the Windows min/max macros.

  • Alternatively redefine min/max to use use the Visual C++ non-standard __min()/__max(). But min/max are still defined as macros and will likely lead to problems. Not a good solution.

  • Alternatively use Visual C++ compiler options /Dmin=__min / Dmax=__max. This will tell compiler and WinDef.h not to define min/max macros and use __min() and __max() functions instead as defined in (or ) so ensure this is included. But min/max are still defined as macros and will likely lead to problems. Not a good solution.

Thanks to Michael Suodenjoki !

OpenCV CPP MarkerDetection – OpenCV를 이용한 마커 인식

OpenCV 라이브러리들을 이용하여 마커의 오리엔테이션과 거리를 측정하는 코드이다. 해당 코드의 핵심부분은 OpenRobotics 블로그를 운영하시는 양광웅 님께서 작성하신 것을 썼다. 원래 프로젝트의 자세한 내용은 이 포스트를 참조하길 바란다. 양광웅 연구원님께서는 내 학부논문 프로젝트를 진행하는데 많은 도움을 주셨다. 이 포스팅을 통해 감사하다고 말씀드리고 싶다.


A code for Marker Detection using OpenCV. The core parts is written by Mr.Yang – OpenRobotics (Korean). Please refer to Document (Korean) for the original project. Mr.Yang helped me a lot for my vision project for Undergraduate Thesis.

image

Prerequisite

  • Linux / Windows
  • OpenCV 2.4.9 (Test is required for higher version of OpenCV.)

Source Code

소스코드와 자세한 내용은 GitHub 저장소를 참조바란다.

Please refer to GitHub repository

APM Quadcopter Autonomous Flight – APM 쿼드콥터 자율비행

지난 8월 6일 오픈소스 멀티콥터 플랫폼인 APM을 이용하여 만든 쿼드콥터 두대를 가지고 저고도에서 GPS 자율 비행을 테스트 하였다.

한대는 APM 2.6 보드를 이용하여 제작했고 한대는 Pixhawk 보드를 이용하여 제작하였다. 자율비행은 launch site에서 이륙 – waypoint로 이동 – 2분간 호버링 – launch site로 귀환하는 미션이다. Mission Planner를 이용해서 미션을 설정하였는데 자세한 방법은 이 문서를 참조바란다. 아래는 비행영상이다.


I and my friend Jaeyoung tested GPS Autonomous flight mode of APM with two quadcopters which embed APM boards.

One embeds APM 2.6 board and another embeds Pixhawk board. Both use ArduCopter firmware. A mission of Auto-flight is taking off from launch site – moving to waypoint – hovering for 2 minutes – returning to launch site. We set mission up with Mission Planner which is a planner for APM platform. Refer to this doc for details.


오른쪽의 쿼드콥터가 APM 2.6 보드를 탑재한 쿼드콥터인데 비행성능이 Pixhawk에 비해서 크게 떨어진다. 특히 바람 등의 외란을 효과적으로 상쇄하지 못하는 것을 확인 할 수 있었다.

비행을 시키다가 배터리 용량을 다 소진하여 fail safe 모드로 진입, 자동으로 착륙하는 것도 확인하였다.

GPS 오차 때문인지 두번째 비행에서는 서로 다르게 설정된 고정 waypoint가 겹쳐져서 충돌하는 사고가 있었다. GPS 데이터가 그리 신뢰롭지 않으니 항상 GPS 자율비행을 할 때에는 넓은 공터에서 주변 사람들에게 위험을 미리 알리고 만약의 상황에 대비할 수 있도록 할 것을 강력히 권고한다.


The right one is the quad which embeds APM 2.6. Its flight performance is worse than the one embeds Pixhawk. Especially it is vulnerable to disturbances like wind.

Since the battery ran out, it went to fail safe mode, landed automatically.

In the second flight, two quads crashed because of the GPS error. It seems GPS data is not accurate enough, so I recommend to be very careful when you try an autonomous flight with GPS.

How to use WIFI in Linux – Network Interface

GUI 환경에서 Linux를 사용할때는 WIFI에 연결하거나 네트워크 환경을 설정하는데 별다름 어려움이 없을 것이다. 대부분의 배포판들은 Mac이나 Windows에서 네트워크를 설정할때와 유사한 GUI 유틸리티를 제공한다.

command line에서 네트워크 설정을 바꾸려면 막막해진다. 처음 Linux를 접한 사람이라면 WIFI에 접속하기도 쉽지 않을 것이다. command line에서 WIFI에 연결하고 싶다면 Network Interface를 수정해서 WIFI에 연결해야 한다. 이 때 이용하는 유틸리티가 networking 이라는 유틸리티이다.

Ubuntu에서는 networking이 Daemon(백그라운드에서 작동시키는 프로세스)으로 동작한다. 부팅할 때 저절로 networking 프로세스가 설정파일대로 동작하는 형식이다.

WIFI에 접속하기 위해선 networking의 설정 파일인 /etc/network/interfaces에 연결하고 싶은 네트워크의 정보에 대해서 입력해주기만 하면 된다.

(이 설정파일을 이용해서 다양한 설정을 할 수 있다. 좀더 자세한 내용에 대해서 알고 싶다면 이 문서 – Debian Wiki 혹은 이 문서를 참조하라.)


Edit Network Interfaces

interfaces 파일을 열기 이전에 먼저 무선랜 장치의 번호부터 확인해보자. 와이파이 모듈이나 무선 랜카드가 연결되어 있다면 wlan0 이런 식의 ID가 부여된다.

아래와 유사하게 출력이 될 것이다. 여기선 wlan2 가 WIFI 모듈의 ID이다.

이제 interfaces 파일을 수정한다.
텍스트 에디터로 interfaces 파일을 열어준다.

다음과 같이 입력해준다.

auto 뒤에 앞에서 찾은 wlan ID를 입력해주고 (YOUR_AP_NAME) 자리에 WIFI 이름을, (YOUR_AP_PASSWORD) 자리에 WIFI 비밀번호를 입력한다.

설정파일을 저장하고 나온 후에, 이제 networking daemon을 재시작해준다.

이제 WIFI가 연결될 것이다. google server에 ping을 날려서 확인해보자.

ping이 정상적으로 간다면 WIFI에 연결된 것이다.

VIM Editor – Changing Color Scheme

코딩을 할 때 Vim 에디터를 쓰는 경우가 많다. Vim 의 가장 큰 장점 중 하나는 커스터마이징이 자유롭다는 점일 것이다. (사실 맨처음에 Vim 을 쓸 때는 폰트나 테마가 너무 단순해서 당황했던 경험이 있다.) 이 포스팅에서는 Vim Editor의 Color Scheme(테마)를 바꾸는 방법에 대해서 간략히 설명한다.

It’s very hard for beginners to understand how to use Vim. But Vim would be a very powerful tool if you customize it.

But before trying to advanced features, let’s try to change color scheme first.


Changing Color Scheme to Molokai

나는 코딩을 할 때 Monokai 테마의 Vim 버젼인 Molokai 테마를 선호하는 편이다. Molokai 테마로 Color Scheme을 바꾸는 것을 예시로 하자. (Linux 기준으로 설명하기로 한다.)

I prefer Monokai theme for text editors. So let me change color scheme to Molokai, which is Vim version of Monokai.


molokai

위 이미지가 Molokai 테마의 모습이다. (이미지 출처 winterdom.com)
배경색을 아래처럼 바꿀수도 있다.

This is Molokai color scheme. (Image from winterdom.com)
You can also change background color as below.

molokai2


  • 이 테마를 적용시켜 보자. 먼저 홈 디렉토리에 .vim 디렉토리를 만들어준다. (이름 앞에 꼭 .을 붙여준다.) 그리고 나서 이 디렉토리 안에 colors 디렉토리를 만들어준다.

  • 이제 Color Scheme을 설치할 차례이다. Molokai 테마 같은 경우에는 GitHub에서 배포되고 있다. 이 repository 를 ~/.vim/colors 디렉토리안에 클로닝하자.

  • 그럼 ~/.vim/colors 디렉토리 안에 molokai 디렉토리가 cloning 된 것을 볼 수 있다. 여기서 우리가 필요한 건 .vim 파일뿐이다. molokai 디렉토리 안에서 molokai.vim 파일을 꺼내서 colors 디렉토리에 옮겨놓고 필요 없는 파일들은 삭제하자.

  • 이제 ~/.vim/colors 폴더 안에 molokai 파일이 있는 걸 볼 수 있다. (확인하고 싶으면 $ ls ~/.vim/colors/ 를 해본다.)

  • Vim으로 .vimrc 파일을 수정할 차례이다. .vimrc 파일에 대해서 자세한 포스팅은 (여기)를 참조하기 바란다. Vim으로 .vimrc 파일을 연다. ($ vim ~/.vimrc) 그 다음에 다음의 내용들을 입력해 넣는다.

위에서 중요한 것이 있는데 set t_Co=256을 반드시 입력해줘야 한다. 안 그러면 molokai 테마의 색이 아닌 다른 색으로 color scheme이 바껴 보일 것이다. 그리고 syntax on도 반드시 해줘야 한다. (syntax on은 프로그래밍 언어 문법을 적용해주는 명령이다.)

당연한 얘기지만 수정한 .vimrc 파일을 저장해줘야 한다. (VIM에서 명령행 모드로 가서 :wq 를 입력하면 저장한 후 쉘로 돌아온다.)

  • HelloWorld.c 를 작성해서 color scheme이 잘 바꼈는지 확인해보자.

Vim Molokai

Build My Own Quadcopter (Slide)

내부 세미나 자료로 만든 나의 쿼드콥터 제작기 슬라이드이다.

(영어 포스팅은 404warehouse 블로그서 읽을 수 있다.)

좀더 높은 화질의 슬라이드를 보고 싶다면 여기에서 볼 수 있다.


This is very brief slides for “Build My Own Quadcopter”

(You can read the post in English at 404warehouse blog.)

If you want to view it via web browser in high resolution, go to here – keynote player


[slideshare id=48801175&doc=buildmyownquad-150531064640-lva1-app6892&w=700&h=525&sc=no]

OpenCV 2.4.10 in Windows7 x64 and Visual Studio 2010

This is manual for installation open source computer vision and machine learning software library, OpenCV in Windows7 x64 and integration with Visual Studio 2010 IDE.

Note : OpenCV 2.4.10 is the last version which officially support Visual Studio 2010 build. If you should use VS2010 for some reasons, It’s recommended to use OpenCV 2.4.10 or below.

Environment

Note!

OpenCV 2.4.10 is compatible with VS2010 but most current version 3.0.0 is not compatible with VS2010.

If you wanna build project in 64 bit. You should use VS2010 Professional.


OpenCV Installation

  1. Extract File. (Extract in D: is recommended)
  2. Set Environment Variables
    1. Open Control Panel → System → Advanced system settings → Advanced Tab → Environment variables
    2. On the System Variables section, select Path (1), Edit (2), and type D:/opencv/build/x64/vc10/bin(3), then click Ok.
    3. If your development environment is not x64 and vc10, set proper path. (ex : D:/opencv/build/x86/vc12/bin for x86 and vc12)

and go to set Integrate OpenCV with Visual Studio…


Integration with Visual Studio 2010 Professional

  • Please refer to this blog post for more detail.
  • You should use VS2010 professional if you want to build project in 64 bit
  1. Create a new project
    1. Open Visual C++ and select File → New → Project… → Visual C++ → Empty Project. Give a name for your project.
    2. Click OK.
  2. Make sure that “Debug” is selected in the solution configuration combobox. Right-click your project and select Properties → VC++ Directories
  3. Select Include Directories to add a new entry and type D:/opencv/build/include
  4. Back to the Property dialog, select Library Directories to add a new entry and type D:/opencv/build/x64/vc10/lib (If your development environment is not x64 and vc10, set proper path. ex : D:/opencv/build/x64/vc12/lib for x86 and vc12)
  5. Back to the property dialog, select Linker → Input → Additional Dependencies to add new entries. On the popup dialog, type the files below:

Note : the libraries above are for debugging. For release, add the libraries below : (If not you don’t need to do this)

Note : You should set platform as x64 if you wanna build in 64 bit

and go to Compile…


Compile

Set build platform (in x64)

  • build in 64bit.
  • please refer to the post

Test Code

  • Test this code for check whether your development environment works well.
  1. Right click your project and select Add → New Item... → Visual C++ → C++ File
  2. Name your file (e.g: loadimg.cpp) and click Ok. Type the code below in the editor:

Test

  1. Put any jpg file in your project folder. (ex : C:/Users/DonghoKang/Documents/Visual Studio 2010/Projects/CVTest2/CVTest2/test.JPG)
  2. Compile the code with type ctrl + F5 key.
  3. It will display the image.

Trouble shooting


References

  • http://docs.opencv.org/3.0-last-rst/doc/tutorials/introduction/windows_install/windows_install.html
  • http://runlearning.blogspot.kr/2014/12/installing-opencv-2410-in-visual-c-2010.html
  • http://dsnight.tistory.com/1
  • http://dream-cy.tistory.com/36
  • http://stackoverflow.com/questions/1865069/how-to-compile-a-64-bit-application-using-visual-c-2010-express

© 2016 EastskyKang

Theme by Anders NorénUp ↑