2016年5月5日 星期四

[心得] 怎麼不導入單元測試


其實這個問題很久了,也研究了不少東西,更試著在各個專案裡面運行過
一開始傻傻地只想著要做單元測試,卻沒考慮那些重要那些不重要,提了不重要的東西出來還會被笑

弄清楚了優先度,可是時程就是只有兩個禮拜要做完,開發都沒時間了哪有時間寫單元測試??

就算有時間,還是可以找到別的事情讓單元測試不能進行 (插件、請假、別的專案、約會、改BUG、...)

更何況一般的專案進行,沒有寫單元測試還是可以開始做的
邊做邊談、邊談邊改、邊改邊上線、邊上線邊測,但必須要說這是很”有效”的方法
一邊可以處理客戶、一邊可以寫程式、一邊可以談需求、一邊測試,根本就到了平行化的一種極致

再者如果開始有機會開始,沒有經驗可能沒有辦法回答,該怎麼寫測試,該怎麼讓測試可以應付修改
怎麼寫才可以讓程式可以被測試、怎樣才能有效的檢核、UI怎麼測、DAL(Data Access Layer)該測嗎?
程式架構都不一樣怎麼測、需求變了變成要改兩次程式耶、都沒有時間開發了哪有時間寫測試?

不得不說,這些是一直發生在身邊的事情,而且真的是蠻嚴重的問題
----
我們要做的事情是怎麼讓系統的正確性提升,最重要的是明確的知道應有的系統行為是甚麼
在各種使用者的合理操作與不合理操作下,系統該有甚麼回應

這些分析完後就是檢核這些案例,腦子裏面檢核、邊開發邊檢核、開發完檢核、客戶檢核、使用者檢核

那麼多的檢核,如果不是依靠更強力的分析,大概是跳過一些檢核階段先不做,反正還有後面來節省時間 (?!?
----
會想寫這篇是因為
最近在邊開發一些工具,把一些周邊常做的事情半自動化
其實只是把常做的事情,透過一些 script 省下時間

從做工具省時間的角度來看... 突然有了一個合理的說法

完成正確的系統需要的不只是單元測試,而是怎麼讓所有繁瑣的檢核能更加快速度

以應付之前的所有理由,而且不是藉由花更多人力、減少測試、改動時程、減少功能來完成

ps. 工具在手不表示人人都是高手,做這件事情是需要練習的
支持測試更完整,支持善用工具加速測試,進行測試從你我做起

2016年5月2日 星期一

[JavaScript] 模組化規範 - CommonJS, AMD, CMD


[JavaScript] 模組化規範 - CommonJS, AMD, CMD

常見的作法... 我們把頁面當作一個一個的模組,借用 jQuery 或是原生的 dom library 進行初始化
jQuery(function{
    // init here
});
//or
document.ready(function(){
    // init here
});
至於功能模組化或是函式庫重用很少在我們的系統中出現
(根據本人不專業觀察)
一方面是業務邏輯等級的設計是真的很困難的,應該只有非常有經驗(重做很多次)的人有辦法處理
指的"絕對"不是只很會寫程式的人,而是很懂 Domain 而且會架構的人
因為分析階段完成後,並沒有有效的銜接設計階段方法,來確切地設計出有彈性地模組
這個情況並不是因為偷懶不進行設計
而是設計完不容易應付修改,尤其只能保證需求一定會改變...
不容易調整的設計,成為殺死團隊的第一把刀

一方面如果系統不是產品,只是一次開發進入維護階段的系統,其實靠人力跟運氣來處理就可以
誰敢重構已經能用的東西,一定是立馬被鞭 (想像是美麗的、現實是殘酷的)
所以這種設計一般發生在共用的字串處理、連線處理、非常常用的業務邏輯元件上
設計到業務邏輯等級,感覺還是在維護階段,針對常修改的功能進行來的合理些

再一方面在台(ㄍㄨㄟˇ)灣(ㄉㄠˇ)了解業務邏輯 、懂設計又那麼閒的工程師,應該蠻快就變成管理職...
(抱怨文完畢,正文開始)
------
CommonJS (http://www.commonjs.org/) 訂定了在非瀏覽器上面的許多 JavaScript 規範,其中正包括模組 (Modules) 。
而在瀏覽器裡面,使用 AMD 模式來處理,這邊主要參考 RequireJS (http://requirejs.org/docs/whyamd.html) 與 AmdJS (https://github.com/amdjs/amdjs-api/blob/master/AMD.md)

CommonJS - Modules/1.0 (http://wiki.commonjs.org/wiki/Modules/1.0)

基本上是這樣,完整點可以直接看定義,另外 1.1 與 1.1.1 版本看起來有再擴充,適用情境也更完整。
模組:使用 exports 變數承接定模組,一般使用檔案名稱當作模組 id
引用:使用 require 函數來指定引用的模組 id
math.js
exports.add = function() {
    var sum = 0, i = 0, args = arguments, l = args.length;
    while (i < l) {
        sum += args[i++];
    }
    return sum;
};
increment.js
var add = require('math').add;
exports.increment = function(val) {
    return add(val, 1);
};
program.js
var inc = require('increment').increment;
var a = 1;
inc(a); // 2

AMD - Asynchronous Module Definition

Module
//Calling define with a dependency array and a factory function
define(['dep1', 'dep2'], function (dep1, dep2) {

    //Define the module value by returning a value.
    return function () {};
});
Named Module
//Calling define with module ID, dependency array, and factory function
define('myModule', ['dep1', 'dep2'], function (dep1, dep2) {

    //Define the module value by returning a value.
    return function () {};
});
Sugar1
define([ "require", "jquery", "blade/object", "blade/fn", "rdapi",
         "oauth", "blade/jig", "blade/url", "dispatch", "accounts",
         "storage", "services", "widgets/AccountPanel", "widgets/TabButton",
         "widgets/AddAccount", "less", "osTheme", "jquery-ui-1.8.7.min",
         "jquery.textOverflow"],
function (require,   $,        object,         fn,         rdapi,
          oauth,   jig,         url,         dispatch,   accounts,
          storage,   services,   AccountPanel,           TabButton,
          AddAccount,           less,   osTheme) {

});
Sugar2 Mixing
define(function (require) {
    var dependency1 = require('dependency1'),
        dependency2 = require('dependency2');

    return function () {};
});
當不是都透過 define 撰寫,還可以使用 requirejs.config() 來更有彈性的設定模組與模組間的 dependency
requirejs.config({

    //Remember: only use shim config for non-AMD scripts,
    //scripts that do not already call define(). The shim
    //config will not work correctly if used on AMD scripts,
    //in particular, the exports and init config will not
    //be triggered, and the deps config will be confusing
    //for those cases.
    shim: {
        'backbone': {
            //These script dependencies should be loaded before loading
            //backbone.js
            deps: ['underscore', 'jquery'],
            //Once loaded, use the global 'Backbone' as the
            //module value.
            exports: 'Backbone'
        },
        'underscore': {
            exports: '_'
        },
        'foo': {
            deps: ['bar'],
            exports: 'Foo',
            init: function (bar) {

                //Using a function allows you to call noConflict for
                //libraries that support it, and do other cleanup.
                //However, plugins for those libraries may still want
                //a global. "this" for the function will be the global
                //object. The dependencies will be passed in as
                //function arguments. If this function returns a value,
                //then that value is used as the module export value
                //instead of the object found via the 'exports' string.
                //Note: jQuery registers as an AMD module via define(),
                //so this will not work for jQuery. See notes section
                //below for an approach for jQuery.
                return this.Foo.noConflict();
            }
        }
    }
});

//Then, later in a separate file, call it 'MyModel.js', a module is
//defined, specifying 'backbone' as a dependency. RequireJS will use
//the shim config to properly load 'backbone' and give a local
//reference to this module. The global Backbone will still exist on
//the page too.
define(['backbone'], function (Backbone) {
  return Backbone.Model.extend({});
});

CMD - Common Module Definition(https://github.com/cmdjs/specification/blob/master/draft/module.md)

math.js
define(function(require, exports, module) {
  exports.add = function() {
    var sum = 0, i = 0, args = arguments, l = args.length;
    while (i < l) {
      sum += args[i++];
    }
    return sum;
  };
});
increment.js
define(function(require, exports, module) {
  var add = require('math').add;
  exports.increment = function(val) {
    return add(val, 1);
  };
});
program.js
define(function(require, exports, module) {
  var inc = require('increment').increment;
  var a = 1;
  inc(a); // 2

  module.id == "program";
});

UMD - Universal Module Definition

只是收攏上面那些定義不過相對彈性也較差
(function (root, factory) { 
    if (typeof exports === 'object') { 
        // CommonJS 
        module.exports = factory(require('b')); 
    } else if (typeof define === 'function' && define.amd) { 
        // AMD 
        define(['b'], function (b) {
            return (root.returnExportsGlobal = factory(b)); 
        }); 
    } else { 
        // Global Variables 
        root.returnExportsGlobal = factory(root.b); 
    } 
}(this, function (b) { 
    // Your actual module 
    return {}; 
}));