어제(23일) 노회찬 의원님이 세상을 등지셨다.

노무현 전 대통령 서거때 느꼈던 아련함이 다시금 다가 온다.

이런 저런 긴 말들을 쓰고 싶지 않다. 내가 느끼는 답답함을 글로 표현하기에는 글 실력이 너무 부족하다.

다만, 내 마음 속에 자꾸 "악화가 양화를 구축한다." 라는 그레샴의 법칙이 자꾸 떠오르는 것은 왜일까... 슬픈 하루다...

 ngrx/store 6.0.1 / Angular 6.x


 ngrx/store 를 처음 써 보는 중에 만나 이슈인데... 어찌보면 당연하긴 하겠지만... 뭔가 magic을 기대하기도 했는데.. 어쨌든...


createSelector(<state>, <action>) 이렇게 될때,  <state> 는 하위의 변화를 detecting하지는 못하는 걸로 보인다. 즉.

state: {
    inner: {
        a: true
    }
}

의 경우

createSelect(state => state.inner, inner => inner.a)

이렇게 해 놓고, dispatch를 통해서, state.inner.a = false 로 하면
selector의 Observer가 fire되지 않는다. 즉, inner.a 는 바뀌었으나, state.inner는 바뀐게 아니라는...


이때 states reference 자체가 reducer에서 바뀌는 경우.. 즉

return state;

가 아니라

return {...state}

가 되면 state의 reference다 바뀌므로 Observer가 fire된다.



Environment: Angular 6.x, Angular Material 6.x


Tooltip애 show 된 이후, Tooltip value에 undefined가 들어가게 되면, 이후 tooltip이 비활성화 되는 것으로 material tooltip component가 처리하는 것으로 보인다.
따라서,
    - html -
    [matTooltip]="myTooltip"

이렇게 한 경우,
tooltip이 'show'된 이후, myTooltip값이
    'a' => 'b' => 'c'
이 순서로 바뀌면, 'c'가 tooltip으로 잘 보이는데,
    'undefined' => 'b'
순서로 replace하면, 'b'가 보이지 않는다. 첫번째 undefined로 인해서, tooltip이 disable되어 버리는 것 같다.

이 경우, mouse를 leave했다가, 다시 enter해서 tooltip이 다시 'show'될 경우, 'b'가 보인다.
즉, 현재 show session에서는 update되지 않고, 다음 'show' session에 보인다.
이것은
    'a' => undefined => 'c'
순서로로 변경할 경우도 마찬가지다 - 당시 show session에서는 'c'가 보이지 않는다.



'Domain > Web' 카테고리의 다른 글

[Angular] ngrx/store state 변화 이해하기... (기초)  (0) 2018.07.11
[Angular6] Inter-module communication.  (0) 2018.06.23

환경:

NodeJS: 8.X

typescript: 2.8.X

jest: 22.X.X

ts-jest: 22.X.X



NodeJS + Typescript를 이용해서 backend를 구성할때, 특정 module을 여러 module에서 공유하고 싶을때가 있다.

이때, 공통 모듈을 package.json 을 가지는 하나의 독립모듈로 구성하고, 이를 dependency에 넣는 것도 좋지만(npm module을 사용하는 일반적인 방법), 그냥 단순하게 module directory를 공용으로 사용하고 싶은 경우도 많다.

예를 들면.


moduleA/

|- package.json

|- src/

|- common (*1)

|- ...


moduleB/

|- package.json

|- src/

|- common (*2)

|- ...


에서 (*1)과 (*2)에서 동일한 코드를 사용하고자 한다고 가정해 보자. 매번 똑같은 파일을 유지하는 것은 문제의 여지가 많으므로, 아래와 같은 구조를 만들고 싶을 것이다.



moduleA/

|- package.json

|- tsconfig.json

|- src/

|- commonA --> (*3) // Symbolic link to (*3)

|- ...


moduleB/

|- package.json

|- tsconfig.json

|- src/

|- commonA --> (*3) // Symbolic link to (*3)

|- ...


shared/

|- commonA/ (*3)

|- ...



그리고, moduleA와 moduleB의 tsconfig.json에는 symbolic link를 쫓아갈 수 있도록 


"compilerOptions": {

...

"preserveSymblinks:" true

...

}


로 설정한다.


자... 이제 모든 것이 문제없이 동작하는 것처럼 보인다.

그런데.. Jest를 수행시키면, 바로 문제가 발생한다.(제대로 Type을 Import하지 못한다!)

그리고, Symbolic link를 제대로 쫓아가지 못하는 것처럼 보인다.

Jest관련 각종 정보를 찾아보면, 일단은, 이 부분은 Known Issue이고, 지원하지 않기로 결정한 것으로 보인다.

(어딘가 본 기억이 있는데, 정확한 link는 잊어 먹었다는... 뭐 이후 또 바뀔수도 있으니....)

그래서, workaround를 고민해 보면, 아래와 같이 그냥 shared module에 ts-jest를 설치해 주면 문제가 해결되는 것 같다.



shared/

|- package.json // only 'ts-jest' exists at 'devDependencies'

|- commonA

|- ...


이렇게 해 두고, shared에서 npm install 을 통해서 ts-jestnode_modules 안쪽에 설치하고 나면, moduleA와 moduleB의 jest가 문제없이 동작하는 것을 볼 수 있다.

더 좋은 방법이 있는지는, 그리고 왜 이렇게 하면 동작하는지는 좀 더 살펴봐야할 부분이긴하나... 굳이 살펴봐야하나... 싶기도 하다... ^_^




Sub module은 내가 어디에 붙어 있는지 몰라야 한다.  즉 module "내부" => "외부" 로 dependency가 없어야 한다. 따라서, Inter-Module Communication은 상위 module을 통하는게 좋아 보인다.


                      +--------------------+
              +------->  Parent  Module    +---+
              |       +---------+----------+   |
              |                 |              |
Parent Module is observing    Notify         Notify
              |                 |              |
    +---------+-+     +---------v-+     +------v---------+
    | Module A  |     | Module B  |     | ...            |
    +-----------+     +-----------+     +----------------+


이 경우, 문제는 Lazy-loading module이다.

Lazy-loading module의 Concept상, Parent Module (이 경우, Root Module)에서 Notify를 할 수 없고 해서도 안된다 - Notify를 하려면, loading을 해야하는데... 그럼 더 이상 lazy loading이 아니다!

(만약 해야할 필요가 있다면, Lazy-loading으로 만들면 안된다. )


따라서, Lazy-loading module의 경우, 일반적인 sub-module의 제약사항에 더불어 추가적으로, 다음의 조건이 필요하지 않나... 싶다.(아직 좀더 많은 공부가 필요하다.)

- App. singleton provider 를 사용하면 안된다.

- module이 loading되었음을 가정한, "외부 => module" 로 communication이 없어야 한다.

<= 이거 상당히 강력한 제약사항이다!!!

그리고, 이건: "Inter-lazy-loading module간 communication이 없어야 한다." 를 의미하기도 한다.


기본적으로, 팀원 들의 팀장 에 대한 평가는 긍정적이기가 어렵다.

자신을 평가하고, 업무를 지시하는 사람에게는 당연히 불만이 생기게 되니... 어찌보면 인지상정이고 당연하다할 수 있다.

그렇다고, 팀원들이 팀장을 전부 안좋게 평가하는데, 이걸 자연스러운 현상이라고 무시할 것인가? 그것도 아닌 것 같다...

그래서 이 부분에 대해서, 개인적인 경험에 비추어 해석의 지표(?) 같은 것을 적어보고자 한다.


일단, 팀원들의 팀장에 대한 평가를 4가지로 분류한다.


적극적인 긍정(적긍)

소극적인 긍정(소긍)

소극적인 부정(소부)

적극적인 부정(적부)


'적극적'과 '소극적' 의 기준이 명확할수는 없으나, 의미는 통할 것이라고 생각한다.


개인적인 경험으로는...


"적부(< 40%) + 소부" <= 60%


까지는 정상적인 범위가 아닐까 한다. 즉 적부 가 40%정도까지, 적부 + 소부 가 60% 정도까지이다.

만약 이 수치가 60%~70% 정도라면, 좀 애매하다...


그리고,


"적부(> 50%) + 소부"  > 80%


라면, leader에게 뭔가 문제가 있다고 볼 수 있을 것 같다.


위의 수치들은 어떠한 근거도 없다. 그냥 내 경험 + 일반화 를 통해 만들어낸 수치일 뿐이다.


수치 자체보다는


"팀장에 대한 팀원의 평가는 좋기가 어렵다"는 것을 전제로, 그 정도가 지나치게 심할 경우는 확인이 필요하다.


정도로 요약하고 싶다.




중드는 거의 본적이 없는데... (영화는 좀 봐도...)

사조영웅전은 소설을 워낙 좋아해서 단편 단편으로 보기도 했던 것 같다.

사조영웅전 83도 봤던거 같고...(워낙 어렸을때긴 하나...) 94도 보고....

물론, 둘다 처음부터 끝까지 쭉~~~ 봤던건 아니고.. 중간 중간 몇몇 편만...

다만, 사조영웅전 94는... 다른 분들도 마찬가지겠지만... 주인 이 워낙 인상깊어서 지금까지도 기억에 남아 있다...

그리고... 이번에 평이 좋다고 해서 2017을 봤는데....


*** 좋았던 점.

- 일반적인 평과 마찬가지로... 어설프게 CG를 남용하지 않았다.

- 배우들의 외모/연기가 전반적으로 훌륭했다.(특히 구양극의 악역이... 그리고 남제는 인상에서 부터 스님의 향기가...)

- 뛰어난 영상미~~

도화도는... 정말 아름답더라는...

몽고 초원도 잘 표현했고...

- '악녀'의 이미지를 포기(?)한 황용은 너무 매력적이었다는... 덕분이 이일동(李一桐 - Li Yitong)씨 팬이 되어 버렸다는...ㅜ.ㅜ 장나라씨 느낌이 있는데... 그리고 나이도 제법 있던데.. (90년생) 너무 귀엽고 이쁘게 나왔다는...ㅜ.ㅜ


*** 불편했던 점.

- 소설이 너무 방대하다보니, 내용을 요약할 수 밖에 없고, 그러다보니, 아무래도 곳곳에 개연성이 떨어지는 부분들이 보인다.

- 배우들이 극 중 나이에 비해서 너무 젊어 보인다. 어떻게 홍칠공 이 얼굴에 주름이 거의 없다냐...

- 캐릭터들 간 무공의 차이가 잘 느껴지지 않는다.

약한 캐릭터의 무술연기나, 강한 캐릭터의 무술연기나 그닥 차이가...

강남칠괴의 무술연기나... 홍칠공의 무술연기나... 항룡18장 빼면.. 그게 그거인듯...

- 원작의 캐릭터 개성이 많이 약화된 곳들이 종종 보인다는... 멋있기만 한 황양사... 별로 악독해 보이지 않고... 부성애 넘치는 구양봉... 진중한 맛이 좀 떨어지는 홍칠공...



*** 호/불호 양면이 모두 존재했던 점.

- 사조영웅전의 기본 내용에 더해, 곽정/황용 의 로맨스를 많이 살린듯...

- 귀여운 악녀! 라는 황용 의 이미지 보다는 사랑스러운 황용 에 초점을 맞춘듯.

- 너무 잘생긴 중신통, 동사,



전체적으로... 상당히 괜찮게 느꼈다. 중국의 엔터테인먼트 산업의 발전속도가 어마어마 한듯... 돈이 몰리니... 질이 올라가는 이치는.. 어찌보면.. 당연하다고 할 수도...


그리고, 좋은 - 사심 가득히 담은 - 여배우 (이일동) 한명을 알게 된 것도 좋고....

이제부터 관련 image들 들어갑니다.


(하기 이미지 관련해서 저작권이나, 기타 문제가 될 소지가 있다면, 알려 주시면 즉시 조치하도록 하겠습니다.)



<포스터?>





<정말... 짧은 단시간 출현인데... 신 스틸하시는 중신통...>





< 너무 아름다운 매초풍... 그래서.. 귀신... 같지 않다는... 구음백골조를 익혔는데도....ㅋㅋ>




<동사... 신조 83에서 양과 양강 역을 했다고 하던데...>




<역시나 짦은 컷이였지만... 정말로, 인자한 스님같은 느낌을 잘 연기했던, 남제 >



그리고... 주인공 커플... 2018. 01월 현재... 실제로 사귀고 있다는 소문도... 양욱문(곽정) 보다 이일동(황용)의 나이가 4살 더 많은 걸로 알고 있는데...






<촬영장 컷 같은데...>










그리고.... 팬심(?)에 이일동 씨 image 무차별로 올립니다....ㅋㅋ







기본 기능은 거의 같으나, 일부 customization이 필요한 module이 있을 경우를 가정하자.
이 경우, 소프트웨어 공학적으로 보면, 기존 module에 melt-in해서 넣는게 좋다.(코드 중복은 가능하면 피해야 한다.)
하지만, 대부분의 일반적인 회사에서, 많은 경우, code를 fork하거나, 비슷한 다른 모듈을 만들게 된다.
왜 그럴까? 내 생각에는 조직논리가 가장 큰 이유로 보인다.

SW 코드에는 여러가지 철학적인 가치판단이 반영되기 마련이다.(변수 명, 함수 명, 코드 설계 방식 등.)
그런데, 하나의 코드를 여러 조직이 관리하게 될 경우, 의사결정시 조율해야 하는 것들이 많아진다.
하나의 조직 혹은 극단적으로 한명이 코드를 관리하면 이런 문제가 없거나, 있더라도, 조직의 leader에 의해 쉽게 조율된다.
그러나, 여러 조직이 엮이는 경우, 의사결정을 위한 협의 과정을 거쳐야 하는데, 이건 거의 모든 개발자들이 피하고 싶어한다.
그러니, 이리저리 논란에 엮이는 것 보다는 코드를 fork하고, 자체 관리를 하는 방향을 택하게 되는 것이다.

즉, 코드 유지보수 비용 vs. 조직간 communication / 조율 비용. 에서 내가 경험한 대부분의 회사에는 개발자들이 후자를 택하게 된다.

그런데, 향후, fork된 코드간 Gap이 커지고, 다양한 fork가 추가적으로 계속해서 이루어지는 과정이 진행되게 마련이다.
그리고 어느 순간, 각 개발자들 입장에서는 여전히 위의 선택(code fork)이 유효하겠지만, 조직(회사) 전체로 놓고 보면 코드 유지 보수 비용이 조직간 조율을 위한 비용을 현저하게 넘어서게 된다.
이때가 되면, 문제를 인지하고, 코드를 정리하는 과정을 거치게 되는데, 이미 refactoring debt 로 인해 상당한 비용을 지불해야할 상황이 된다.

위와 같이, 별도의 조치가 없다면, 결국 조직 혹은 협업의 문제로 인해, 코드 중복은 자연스럽게 발생하게 된다.
그리고, 대부분의 경우, 뒤늦은 refactoring으로 인해 많은 비용을 지불하게 된다.(refactoring debt)

이 문제를 해결하기 위해서는, 결국 중앙 집중적인 의사결정 기구 및 monitoring 기구가 필요하게 되는데, 이 역시 쉽지 않다.
신속하고, 때로는 강력하게 의사결정을 해 나가더라도, 다른 개발자들이 이를 존중해 줄 수 있어야 하는데... 이게 쉬울 리 없다...ㅜ.ㅜ

이상이, 내 개인적인 "문제에 대한 진단" 이다.
해결책은... 각 회사/조직/집단 의 상황에 맞게 알아서 잘~~~




Node 8.9.0 기준

ChildProcess.exec는 shell을 통해서 command를 수행한다.
https://nodejs.org/api/child_process.html 의 아래 설명 참조.


child_process.exec(command[, options][, callback])
    ...
    shell <string> Shell to execute the command with. Default: '/bin/sh' on UNIX, process.env.ComSpec on Windows. See Shell Requirements and Default Windows Shell.
    ...


그래서 주어진 command는 실제로는 "shell => command"로 수행이 되며, exec(...)에 의해서 return되는 process는 shell process이다.
(주의: user command에 의해 수행되는 process가 아니다!)
따라서, return된 process를 kill 해도 실제 user command의 process가 kill되지는 않는다.

이 문제는, exec대신 'execFile' 혹은 'spawn'을 통해서 해결 가능하다.
아래는 spawn관련 document이다.


child_process.spawn(command[, args][, options])#
    ...
    shell <boolean> | <string> If true, runs command inside of a shell. Uses '/bin/sh' on UNIX, and process.env.ComSpec on Windows. A different shell can be specified as a string. See Shell Requirements and Default Windows Shell. Default: false (no shell).
    ...

즉 spawn은 'shell'을 통하지 않고 바로 수행가능하다.
따라서,

let child = spwn(command[, args], {shell: false})
child.kill()

와 같은 방식은 정상적으로 동작한다.

혹은 execFile 은 기본적으로 shell을 통하지 않으므로, child.kill()이 정상 수행된다.





<<< co.ts >>>
//
// Module to help escape from callback-hell by using semi-coroutine(generator).
//

function co<E, T>(generator: GeneratorFunction,
        callback: (err: E, result: T) => void) {
    function *wrapper(resolve: any, reject: any) {
        let wrapperGenerator: Generator = yield;
        try {
            let result = yield* (<any>generator)(wrapperGenerator);
            resolve(result);
        } catch (e) {
            reject(e);
        }
    }

    new Promise<T>((resolve, reject) => {
        let w = wrapper(resolve, reject);
        w.next(); // run function
        w.next(w);
    }).then(r => {
        callback(<any>undefined, r);
    }, e => {
        callback(e, <any>undefined);
    });
}

export = co;


<< main.ts >>

import co = require('./co');


describe('co', function() {
    it('co success', done => {
        co<string, Array<Number>>(<any> function* (gen: Generator) {
            let path = new Array<Number>();
            path.push(10);
            setTimeout(() => {
                path.push(20);
                gen.next();
            }, 200);
            yield;
            path.push(30);
            return path;
        }, (err, result) => {
            assert.ok(undefined === err
                && 3 === result.length
                && result[0] === 10
                && result[1] === 20
                && result[2] === 30);
            done();
        });
    });

    it('co fails: throw in generator', done => {
        co<string, string>(<any> function* (gen: Generator) {
            setTimeout(() => {
            }, 200);
            if (gen) {
                throw 'error';
            }
            return 'hello';
        }, (err, result) => {
            assert.ok(undefined === result
                && 'error' === err);
            done();
        });
    });

    /*
     * This test gives unexpected error at mocha. But works well at normal environment.
     *
     *
     * (node:41022) UnhandledPromiseRejectionWarning: Unhandled promise rejection...
     * (node:41022) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated....
     * node_modules/mocha/lib/runner.js:690
     * err.uncaught = true;
     *            ^

     * TypeError: Cannot create property 'uncaught' on string 'error'
     *     at Runner.uncaught ...
     *     ...
     *

    it('co fails: Generator.throw', done => {
        let path = new Array<Number>();
        co<string, string>(<any> function* (gen: Generator) {
            setTimeout(() => {
                gen.throw!('error');
                path.push(10);
                return;
            }, 200);
            return 'done';
        }, (err, result) => {
            assert.ok(undefined === result
                && 'error' === err
                && 1 === path.length);
            done();
        });
    });
    */
});


+ Recent posts