기본 기능은 거의 같으나, 일부 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();
        });
    });
    */
});


여기서 <뛰어난> 이란, 아~주 특출나게 뛰어난 경우가 아닌, 상위권 수준으로 뛰어난 사람을 뜻한다.

<뛰어난 사람>
* 기술적인 성과를 이루었다.
=> 대단한 일이 아니라고 생각한다. 왜나햐면, 자기 자신에 비추어 생각해 보면, 이건 그리 어려운 일이 아니였으므로...

<평범한 사람>
* 기술적인 성과를 이루었다.
=> 자기 자신에 비추어 봤을 때, 대단한 성과라고 생각하므로 적극 홍보/자랑 한다.

그런데, 보통, senior manager group은 기술의 세부적인 내용에 대한 이해가 부족하다.
그리고, 대부분의 경우, 성과를 판단하는 요소에, 실제 기술적인 성과는 상당히 많이 간략화 되기 때문에, 홍보/포장 이 많은 비중을 차지하게 된다.

물론, 기술 자체는 그 활용성이 없다면 의미가 없다. 하지만... 기술적인 성과는 차후 개선의 밑바탕이 될 수 있으므로, 여전히 중요하다.
-------------

극단적인 생각이긴 한데... 이런 모습이 조직에서 종종 보이는 것 같다.
다시 말하지만, 극단적이다. 그렇지만, "위와 같은 모습이 종종 보이더라..." 라는 것 자체도 언급할 가치가 있지 않을까?


< 좋은 link >
http://insanehong.kr/post/javascript-prototype/



< From: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new >

The new operator creates an instance of a user-defined object type or of one of the built-in object types that has a constructor function.

...

When the code new Foo(...) is executed, the following things happen:

A new object is created, inheriting from Foo.prototype.
The constructor function Foo is called with the specified arguments, and with this bound to the newly created object. new Foo is equivalent to new Foo(), i.e. if no argument list is specified, Foo is called without arguments.
The object returned by the constructor function becomes the result of the whole new expression. If the constructor function doesn't explicitly return an object, the object created in step 1 is used instead. (Normally constructors don't return a value, but they can choose to do so if they want to override the normal object creation process.)


< From: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/prototype >

...

Object.prototype.constructor
    Specifies the function that creates an object's prototype.

...


--------
즉, 'prototype' property는, 결국 'new' operation에서 object inherit을 위해 사용되는 reserved property이고, instance의 prototype chain은 [[Prototype]](de-facto __proto__) 에 의해 관리된다.

이상에서 다음을 알수(유추할수?) 있다.
- 'prototype' property 는 parent를 가리키는 일반적인 object 이다. (*1)
    : Default로, Function은 자기자신을 constructor로 가지는 prototype을 가진다.
- [[Prototype]]은 'prototype' object 를 가리킨다.

=====================
확실한 이해를 위해 'new' operation의 동작을 확인해 보자.(Tested on console of Chrome-63)


> function A(name) {
    this.date = Date.now();
    this.x = 'Proto';
    this.name = name;
}
undefined

> a = new A('a')
A {date: 1514262817152, x: "Proto", name: "a"}

> b = new A('b')            <= date 값이 매번 달라진다.
A {date: 1514262824795, x: "Proto", name: "b"}

> A.prototype                <= constructor만을 가지는 아주 단순한 object
{constructor: ƒ}
    constructor: ƒ A(name)
    __proto__: Object        <= Object Prototype


> c = Object.create(A.prototype);    <= 단순하게 constructor를 상속하는 과정이라고 생각할 수도 있다.
A {}

> c.constructor('c')            <= Prototype의 constructor를 새롭게 생성된 object에 binding해서 수행한다. (Same with, c.__proto__.constructor.call(c, 'c'))
undefined                   Note: binding of 'this' keyworkd (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this)
                       Note: c.__proto__.constructor('c') <= 잘못된 수행. prototype object 자체를 더럽히게 된다.
> c.constructor === c.__proto__.constructor
true

> c
A {date: 1514262907466, x: "Proto", name: "c"}



Working environment:
OS: Linux 4.4.0-104-generic #127-Ubuntu SMP Mon Dec 11 12:16:42 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
nodejs: 8.9.1
typescript: 2.6.2




<< class decorator >>


(() => {
///////////////////////////////////////////////////////////////////////////////
//
// Class decorator
//
///////////////////////////////////////////////////////////////////////////////
/**
 * See ClassDecorator type at lib.es5.d.ts
 * @param ctor constructor function. Prototype of constructor function
 *               is object for class-definition.
 */
function log<T extends Function>(ctor: T): T | void {
    // decorator is called with 'this' bound to global-object,
    console.assert(undefined !== this.console);
    console.assert(console === this.console);
    console.assert('function' === typeof(ctor));
    console.log('%o', ctor);
    let newConstructor: any = function (...args: Array<any>) {
        console.log('New: ' + ctor.name);
        let o: any = new (<any>ctor)(...args);
        // this is new constructor. So, '_test' is shown at console.log(<instance>)
        o._test = 'mytest';
        return o;
    };
    return newConstructor;
}

@log
class Person {
    constructor(
        public name: string,
        public surname: string
    ) { }

    get myname() {
        return this.name;
    }

    fullname(nick: string) {
        return `${this.name} ${this.surname} : ${nick}`;
    }
}

let p = new Person('name', 'surname');
// console.log('%o, %O', p, p);
console.assert(p.name === 'name');
console.assert(p.myname === 'name');
console.assert(p.fullname('N') === 'name surname : N');
console.assert((<any>p)._test === 'mytest');


///////////////////////////////////////////////////////////////////////////////
//
// Class decorator with argument.
//
///////////////////////////////////////////////////////////////////////////////
function logArg(nick: string) {
    return function<T extends Function>(ctor: T): T | void {
        let newCtor: any = function (...args: Array<any>) {
            // console.log('New: ' + fconstructor.name);
            let o: any = new (<any>ctor)(...args);
            o.nick = nick;
            return o;
        };
        return newCtor;
    };
}

@logArg('nick')
class Person2 {
    constructor(
        public name: string
    ) { }

    fullname(age: number) {
        return `${this.name} ${(<any>this).nick} : ${age}`;
    }
}

let p2 = new Person2('name');
// console.log(p);
console.assert(p2.name === 'name');
console.assert(p2.fullname(18) === 'name nick : 18');
console.assert((<any>p2).nick === 'nick');


///////////////////////////////////////////////////////////////////////////////
})();









<< method decorator >>

(() => {
///////////////////////////////////////////////////////////////////////////////
//
// Member decorator
//
///////////////////////////////////////////////////////////////////////////////

/**
 * See MethodDecorator at lib.es5.d.ts for detail types.
 * @param target class(prototype of class-constructor) where this method belongs to
 * @param key name of method
 * @param descriptor method(function)-property descriptor
 */
function log(
    target: any, key: string | symbol, descriptor: TypedPropertyDescriptor<any>
): TypedPropertyDescriptor<any> | void {
    // decorator is called with 'this' bound to global-object,
    console.assert(undefined !== this.console);
    console.assert(console === this.console);

    console.assert('fullname' === key);

    // This is ONLY FOR DEMO and EXAMPLE! - NOT RECOMMENDED!
    // Add new property to prototype of class-constructor.
    // (applied to all class instance!)
    target['newattr'] = 'hello';

    // save a reference to the original method this way we keep the values currently in the
    // descriptor and don't overwrite what another decorator might have done to the descriptor.
    if (descriptor === undefined) {
        descriptor = Object.getOwnPropertyDescriptor(target, key)!;
    }

    //editing the descriptor/value parameter
    let orig = descriptor.value;
    descriptor.value = function () {
        console.log(`Called: ${key}`);
        return orig!.apply(this, arguments);
    };

    return descriptor;
}


class Person {
    constructor(
        public name: string,
    ) { }

    get myname() {
        return this.name;
    }

    @log
    fullname(newname: string, nick: string) {
        this.name = newname;
        return `${this.name}:${nick}`;
    }
}

let p = new Person('name');
let p2 = new Person('name2');
// console.log(p.fullname('newname', 'nick'));
console.assert('name' === p.myname);
console.assert('name2' === p2.myname);
console.assert('hello' === (<any>p).newattr);
console.assert('hello' === (<any>p2).newattr);
console.assert('newname:nick' === p.fullname('newname', 'nick'));
console.assert('newname' === p.myname);
console.assert(`hello` === (<any>p).newattr);

console.log(p);

///////////////////////////////////////////////////////////////////////////////
//
// Member decorator with argument
//
///////////////////////////////////////////////////////////////////////////////

// Same way with class decorator.

///////////////////////////////////////////////////////////////////////////////
})();





<< parameter decorator >>

(() => {
///////////////////////////////////////////////////////////////////////////////
//
// Parameter decorator
//
///////////////////////////////////////////////////////////////////////////////

/**
 *
 * @param target class(prototype of class-constructor) where this method belongs in
 * @param key name of method where this parameter belongs to
 * @param index parameter position in function - stars at 0(the first parameter).
 */
function log(target: any, key: string | symbol, index: number): void {
    // decorator is called with 'this' bound to global-object,
    console.assert(undefined !== this.console);
    console.assert(console === this.console);

    console.assert('fullname' === key);
    console.assert(1 === index);
}


class Person {
    constructor(
        public name: string,
    ) { }

    get myname() {
        return this.name;
    }

    fullname(newname: string, @log nick: string) {
        this.name = newname;
        return `${this.name}:${nick}`;
    }
}

let p = new Person('name');
// console.log(p.fullname('newname', 'nick'));
console.assert('newname:nick' === p.fullname('newname', 'nick'));
console.assert('newname' === p.myname);

///////////////////////////////////////////////////////////////////////////////
})();






<< property decorator >>

(() => {
///////////////////////////////////////////////////////////////////////////////
//
// Property decorator
//
///////////////////////////////////////////////////////////////////////////////

/**
 *
 * @param target class(prototype of class-constructor) where this method belongs in
 * @param key name of method where this parameter belongs to
 */
function log(target: any, key: string | symbol): void {
    // decorator is called with 'this' bound to global-object,
    console.assert(undefined !== this.console);
    console.assert(console === this.console);
    console.assert('name' === key || 'obj' === key);

    let internalKey = `$${key}`;
    // property getter
    let getter = function () {
        console.log(`Get: ${key} => ${this[internalKey]}`);
        return this[internalKey];
    };

    // property setter
    let setter = function (newVal: any) {
        console.log(`Set: ${key} => ${newVal}`);
        this[internalKey] = newVal;
    };

    // Delete property.
    if (delete target[key]) {
        console.assert(undefined === target[`$${key}`]);
        // Create new property with getter and setter
        Object.defineProperty(target, key, {
            get: getter,
            set: setter,
            enumerable: true,
            configurable: true
        });
    }
}

class Person {
    @log
    public name = 'init';
    @log
    public obj = {};

    constructor(name: string) {
        this.name = name;
    }

    get myname() {
        return this.name;
    }

    fullname(newname: string, nick: string) {
        this.name = newname;
        return `${this.name}:${nick}`;
    }
}

let p = new Person('name');
p.name = 'hello';
p.obj = {a: 1};

console.assert((<any>p)['$name'] === p.name);
console.assert((<any>p)['$obj'] === p.obj);
console.assert('newname:nick' === p.fullname('newname', 'nick'));
console.assert('newname' === p.myname);

// console.log('%o', p);

///////////////////////////////////////////////////////////////////////////////
})();


+ Recent posts