2019년 11월 22일 금요일

[javascript] jQuery는 어떻게 new가 없이 생성되는 것일까?

언젠가 개발을 처음 시작할 때 나프다의 [정개발]이라는 분과 슬랙 대화를 한 적이 있다. 당시 나는 자바만을 배우고 있었음에도, 그분은 언젠가 jQuery소스를 공부해야 한다고 했었다.
그때는 자바 개발하기에도 바쁜데 자바스크립트에 제이쿼리를 사용하는 것도 모자라 소스를 공부?
라고 생각했었다.

지금은 자바스크립트를 배우는 것이 자바에도 도움되고 더 나아가 내가 어떤 언어를 짜던 잘나가는 오픈소스를 읽는 것이 중요하다고 생각한다. (하지만 제대로 소스를 판 적은...)
오늘은 제이쿼리에게서 한 수 배워보고자 한다.

오늘 주제는 jQuery는 어떻게 new를 없앴는가? 이다.
대부분 new를 없앤다는 것은 생성자를 사용하지 않는 다는 말과 같다. 매개변수가 필요하면 그 매개변수를 클로저로 덮어서 함수로 던지는 것이다. 그러면 그 함수의 스코프는 자기 함수 밖에 있는 변수(매개변수 포함)를 덮은(closure) 채로 세상 밖에 내던져진다.

하지만 jQuery는 그런 것 같지 않다. 어떻게 그렇지 않은지 아주 쉬운 방법으로 알 수 있다.


이처럼 __proto__안에 수 많은 함수들이 있는 것을 알 수 있다. 어쩌면 당연한 것이다. 수 많은 기능들을 사용할 때마다 각 Execution Context에 포함해서 던진다는 말은 사용할 때마다 동일한 기능들이 메모리에 올라간다는 말이니까. 한 두개면 괜찮겠지만 저게 몇 개인가!

new를 사용하는 것이 분명하다.

그렇다면 jQuery소스를 한번 구경해보자.

jQuery($)의 정체

제이쿼리 사이트에서 일반 개발용소스를 참고했다.(https://code.jquery.com/jquery-3.4.1.js)

일단 jQuery를 실행하면 무엇이 나오는가? 그것이 시작일 것이다. 일단 jQuery는 함수일 것이다. 그러니까 new가 없이 실행되는 것이다. jQuery라는 이름의 함수를 찾아보자.

var version = "3.4.1",
  // Define a local copy of jQuery
  jQuery = function( selector, context ) {
  // The jQuery object is actually just the init constructor 'enhanced'
  // Need init if jQuery is called (just allow error to be thrown if not included)
      return new jQuery.fn.init( selector, context );
  },
...


그렇다. jQuery를 실행하면 내부적으로 jQuery.fn.init이라는 생성자를 실행한다!

jQuery.fn.init의 정체

이제부터는 jQuery의 소스를 이용하면, 말이 많아지기 때문에, 제이쿼리의 간단한 구조를 간단히 설명하고자 한다. 조그만한 자바스크립트 라이브러리를 만들었다고 해보자.

/*
제이쿼리 구조를 설명하기 위해 만든 간단한 함수
*/
(function (global) {
  var mini = function(name) {
    return new mini.fn.init(name);
  }

  mini.fn = mini.prototype = {};

  mini.fn.sayHi = function() {
    console.log("hi " + this.name);
    return this;
  }

  mini.fn.sayBye = function() {
      console.log("bye " + this.name);
      return this;
  }

  mini.fn.init = function(name) {
    this.name = name;
  }

  mini.fn.init.prototype = mini.fn;
  global.mini = mini;
})(window);

var M = mini("Nam");
M.sayHi().sayBye();

녀석의 이름은 mini다. 즉시실행함수를 만들어서 스코프가 밖으로 나가지 못하도록 하였으며 글로벌에 mini라는 변수를 넣어야 하기 때문에 window를 매개변수로 넣었다.

jQuery에서는 이 global이라는 녀석을 받아서 jQuery에서 사용할 수 있는 녀석인지 유효성 체크도 한다.
그리고 mini를 실행하면 mini.fn.init 이라는 객체를 뱉을 것이다.
한번보자.

실행해보자.
var M = mini("nam");
M.sayHi().sayBye();

잘 보면 두 함수를 연속적으로 실행할 수 있는데, 이건 다른 라이브러리에서도 많은 보았을 것이다. 이렇게 함수를 연결하는 방법은 우리가 사용하는 객체를 계속 리턴하는 것이다.
그런데 왜 prototype이 아니라 fn이라는 녀석에 넣은 것일까?

아마 계속 쓰일 녀석이라서 fn으로 간편화 한게 아닌가 추측을 하지만 정확한 이유는 모른다.
이렇게 new가 없이 객체를 생성하는 방법을 알아보았다.

2019년 11월 20일 수요일

[java] 상속 대신 구성을 사용하자.

왜 이 글을 쓰는 걸까

나는 자바 개발자로 일을 하면서 주위 사람들과 계속 이야기 하였다. 하지만 다들 수긍을 하는 듯 하면서 절대 바뀌지 않는 것이 있었다. 바로 **상속 대신 구성**이라는 말이다. 꽤나 오랜시간 구성을 사용하자고 설득하였지만 아무도 변화시키지 못했다. 나의 능력이 부족해서 속상하면서도 한 편으로는 내 말을 듣지 않는 것 같은 동료들이 마냥 아쉬웠다.

그런 의미로 나의 주장을 정리하고 가다듬어야 겠다는 생각을 했다.

내가 처음 자바를 공부할 때부터 들어온 말이 있다. 재사용을 위해 상속을 사용하라. 코드의 재사용을 위해서는 상속을 사용해야 한다. 두 개의 코드가 중복되면 상속을 해라. 그런데 뭔가 이상한 것을 느꼈다. 한 명이 말 한 것이 아니며 나에게 자바를 처음 알려준 강사부터 일터에 나가서 까지 그들은 마치 개발의 시작과 끝은 extends라는 키워드만 알면 다 할 수 있는 것으로 설명했다.

마치 상속이라는 단어가 마법의 단어이며, 이것만 있으면 다른 것을 알 필요도 없고 객체지향이건 유지보수 가능한 구조이며 모든 것이 가능하다고 설파했다.
그런데 막상 시간이 지나면 "이건 뭔가 잘못된 구조야", "아! 이건 상속을 좀 잘못했군. 제대로 했어야지", "앗! 이건 못 고친다. 이거 고치면 다 바꿔야해!" 상속은 점점 사람들을 옥죄었고 무엇이 잘못되었는지 몰랐다.

다들 너무 착하다. 사람들은 마치 상속으로 재사용 도모하라는 책들의 말을 믿고 구조가 이상하게 되면 마치 자기 탓인 것 마냥 아파한다. 자기 머리를 쥐어뜯는다. 하지만 끝까지 상속의 잘못은 아니라고 한다.

하지만 이건 그들의 잘못이 아니다 상속이라는 상아탑을 맹신하게하는 분위기가 그들의 도전을 막는다. 상속은 왠지 클래스 사이에 절대 바뀌지 않을 거라는 약속을 하며 새끼손가락을 거는 것 같다. 장기처럼 한 번 두면 무를 수 없는 약속이었다. 나는 이런 약속을 하고 싶지는 않았다. 무조건 깨질 것인데
후에 이 상속 구조를 자유롭게 수정할 수 있을까? 정말 이 구조가 맞는 걸까? 나중에 이 구조가 틀려서 바꿔야 한다면 바꿀 수는 있는 것일까?

없을 것 같았다. 시간이 지나면 지날수록 상속으로 끈끈하게 만들어진 유대관계는 깨기가 힘들어 보였다. 상속계층의 위로 올라갈 수록 그 약속이 많아지기 때문에 수정되면 안된다. 가령 자바에서 가장 위에 있는 객체는 `Object`이다. 이 녀석은 왠만해서는 절대 바뀌지 않는다. 수많은 자식들이 자신을 쳐다보고 있다. 그 자식들(서브클래스)은 당신(Object)이 변하지 않는다는 것만 믿고 재사용을 하기로 한 것이다.

상속은 마치 서브 클래스가 부모 클래스를 위에다가 그대로 복사해서 쓰는 것과 비슷하다. 만약 부모가 바뀐다고 해보자. 서브클래스도 당연히 바뀐 것이다.


2003년도부터 혹은 그 이전부터 상속은 문제였다.

https://www.javaworld.com/article/2073649/why-extends-is-evil.html
someone : If you could do Java over again, what would you change?
Gosling : I'd leave out classes
고슬링은 클래스를 없애고 싶다고 했다. 덧붙이자면 클래스 자체에는 문제가 없으나 extends관계가 문제이며 `implements`관계를 선호하라고 한다.


어떤 개발자는 자신의 99%의 코드에 상속을 사용하지 않았다고 한다

https://codeburst.io/inheritance-is-evil-stop-using-it-6c4f1caf5117
위 블로그에 있는 내용을 인용하겠다.
Using inheritance is not the only way to extend a class behavior.
But definitely is the most dangerous and harmful one.

I do not use inheritance in the 99% of my code.
And I really mean 99%.

- use interfaces to define the contracts between your classes.
- use final classes to implement behavior for those interfaces.
- use composition (through constructor dependency injection) to put things
together and prevent complexity


[이펙티브 자바]에서는 상속 대신 구성을 사용하라고 권고한다.

자세한 건 책을 참고


[오브젝트]에서는 상속을 절대 재사용을 위해 사용하지 말라고 한다.

계층구조를 표현하기 위해 사용해야 한다고 말이다. 즉 타입을 상속하기 위해서 쓰라는 말이다.


C++창시자분도 유튜브로 상속이 없이 어떻게 개발해야 하는지 발표하는 시대가 왔다.

세상은 정말 많이 변했다. 비야넷 아저씨도 변한 세상에 따라 자신의 주장을 바꿨다.

내가 정리한 문제점

내가 생각하는 상속의 문제점은 아래와 같다.
1. equals 구현에 문제가 생긴다(추이성)
2. 상속을 할 때 부모클래스 중에 구현된 기능이 있다면, 그것을 제대로 알고 구현을 해야 한다. 이 말은 하나의 소스코드가 둘로 쪼개져 있는 것과 같다.
3. 부모클래스가 변경되면 자식클래스 또한 전부 점검해야 한다.
4. 자식클래스가 바뀔 때, 부모클래스를 안 볼 수가 없다.
5. 버그를 찾기가 힘들다. 누구의 책임인지 확인하기가 쉽지 않다. 특히 `super`를 함께 쓰게되면 마치 저 클래스 멀리 있는 코드가 내 코드처럼 행동하면서 핑퐁을 하기 시작한다. 위로갔다 내려왔다 롤러코스터를 타게 된다.

그러므로 나는 상속보다는 `Composition` 소위 구성이라는 기능이 기본이 되어야 한다고 생각한다. `Composition`은 상속과 다르게 내부 구현을 알 수 없다. 필요한 부분에만 책임을 위임한다. 내부 소스로직이 수정되었다고 다른 소스에 문제를 일으키지 않는다. 위임한 책임만 완수하면 어떻게 바뀌던 상관이 없다. 하지만 상속은 문제를 끼칠 수 있다. 구성은 그래서 수정에 자유롭다. 아니 아예 전부 뜯어 고쳐서 새로 넣어도 된다. 그렇게 하라고 의존성 주입이 있는 거니까.

방금 구성을 사용해야 하는 다른 이유가 나왔다. 구성은 런타임에 의존성이 바뀔 수 있다. 즉, 프로그래밍이 실행 중에도 내 할일을 다시 집어넣을 수 있다는 말이다. 원래라면 하늘 아래 부모클래스는 하나밖에 가질 수 없고, 그것은 컴파일 이후에는 운명처럼 거스를 수가 없는데 말이다.


결론

글을 쓰면서 내가 틀렸던 건 아닐까 하는 생각으로 정리를 해보았으나.
틀리지 않은 것 같다. 오히려 자신감이 생긴 것 같다.

상속을 쓰기 전에 구성을 할 수 있는지 확인하자. 어떻게 해서든지 구성을 사용하려고 해보자. 코드에 더 믿음이 갈 것이다. 클래스에 final을 붙여서 상속을 막아 보자. 나 자신뿐만 아니라 사람들이 그 코드를 믿게 될 것이다. 상속보다 구성으로 짠 코드를 볼 때 더 별거 아닌 코드라고 생각하는 사람들이 많다. 그것이 목적이다. 더 별거 아닌 것처럼 보이게 더 쉽게 읽을 수 있게 말이다.