Eisen's Blog

© 2024. All rights reserved.

最近写 chrome extension 的一些心得

2012 June-10

有幸找到一个可以专门写 chrome extension 的工作,非常开心,可以专心的写 chrome extension 了。看了一些别人的 chrome extension 自己也有了一些新的体会,写在这里,留个未来的自己。

最近在做的这个扩展是 mei.fm 的一键收藏扩展。整体来说,这个扩展的功能并不是非常复杂。但是,再简单也是有一定工作量的。而且,程序是为变化而生的,最初的架构体系做的比较顺手对以后的工作也是有好处的。

调研

首先,我做了一些调研的工作。重点查看了 evernote clipper 以及 pocket 两个类似的产品。看了他们之后才知道做一个这样的插件没有表象那么简单。尤其是读了 pocket extension 的源码确实是给我了不少的启发。

pocket 里面有一个很不错的结构就是让 background page 作为处理事务的中枢。让 content page 以及 popup 的所有请求利用 chrome extension 所提供的 sendRequest 的 api 发送给 background 去处理,然后 bg 将处理的结果以 callback 的形式发送回去。

chrome.extension.sendRequest(string extensionId, any request, function responseCallback)

chrome.extension.onRequest.addListener(function(any request, MessageSender sender, function sendResponse) {...});

详见https://code.google.com/chrome/extensions/tabs.html 以及 https://code.google.com/chrome/extensions/extension.html

这样做的好处就是可以更好的组织自己的代码,让 content page 以及 popup 去专注于页面的展示,而把逻辑以及 ajax 的东西集中在 bg 去做。比如,在 pocket 中会用到一个 content script 用来显示保存的状态。它每次动作对 bg 发送请求,让 bg 把链接保存到 pocket,然后它根据 bg 返回的信息,进行相应的信息提示的工作。

图丢了

实践

代码的组织

我很仔细的看了 pocket 的代码组织并非常的认同它的组织方式,那么我就直接采取了它的结构。把与 mei.fm 请求相关的方法单独做一个命名空间,api。然后所有其他页面的逻辑请求都通过 sendRequest 的方式发送给 bg.js 来处理。但是,chrome extension 的 onRequest 监听器不监听自己页面的 request 事件,那么我只好对 bg 页面的处理单独做了处理(这个很让人伤心)。

面对频繁改变的 api 接口的办法

其实我觉得既然已经做到了 chrome extension 的地步了,那么其提供的 api 应当是比较稳定的了。但是,很遗憾,不是这样。甚至是在我把插件做完的时候,有些 api 还是处于无法使用的状态。那么,我自己需要一个模拟 api 的机制了。

    if(debug) {
      var data = JSON.parse('{"requestId":"url68a1-9","providerId":"solrs-0.1","object":"url","errCode":0,"dice":0,"items":[{"id":"5c9ccc75421a2acbffa982f5fd123134","score":0.92352885,"provider":"solrs-0.1","detail":{"best_title":"皇马拜仁裁判确定:欧冠奥运决赛主裁 吹拜仁漏 3 点球","url":"http://sports.sina.com.cn/g/2012-04-24/09206035821.shtml"}},{"id":"051e0cd0943d490f51cb7be1a502dbb9","score":0.24958704,"provider":"solrs-0.1","detail":{"best_title":"视频-2011 百大进球 TOP20 梅西 C 罗鲁尼内马尔竞风流","url":"http://sports.sina.com.cn/g/video/2011Top100goal/index.shtml"}}]}');
      setTimeout(function() {
        if(data.items && data.items.length) {
          data = _pre_process(data);
          console.log(['cmd', data]);
          callback && callback.success && callback.success(data.items);
        } else {
          callback && callback.failed && callback.failed();
        }
      }, 1000);
    } else {
      $.ajax({
        url: request_url,
        data: {title: title, uid: localStorage.uid, cnt: 2, ts: 0, app: 'mei.fm', url: url},
        timeout: 5000,
        dataType: 'json',
        success: function(data) {
          if(data.items && data.items.length) {
            data = _pre_process(data);
            callback && callback.success && callback.success(data.items);
          } else {
            callback && callback.failed && callback.failed();
          }
        },
        error: function() {
          callback && callback.failed && callback.failed();
        }
      });
    }
  }

用 setTimeout 去模拟一个 ajax 请求真是伤不起唉。然后面对返回的数据结构的不确定,我甚至有做个数据结构验证的东西...

不要忘了断网的情况

虽说这是个特殊的情况,但是我还是通过设置 ajax 的 timeout 去做了这个断网的处理,所以调用 jquery 的 api 差不多全部都是 $.ajax 而不是 $.post 或者 $.get。

感想

不管东西是多大多小,想做的完美其实都是要下功夫的。面对频繁的变化,你的代码是否可以让你很容易的扩展,或者是修改功能呢?我自己是不敢保证的。努力去做到这个才能算是不错的设计。我还是需要在这条路上走的更远。

然后就是单元测试的问题了。代码的修改是不可避免的,尤其是做所谓的重构。但是,目前来看,我很不想去做这个工作。因为我没有想到好的办法去做这个单元测试的工作。如果修改了代码反而让程序跑不起来了确实就让人更揪心了。如何拆分代码,并用更好的办法去测试代码也是个很重要的事情。

更好的使用 git 有可能很好的减少自己的心理负担。什么时候应该打 tag 怎么撤销自己的一个 commit 怎么去拉一个分支都是我需要做学习的事情。


POJ 1018

2012 May-15

#include <iostream>
#include <cstdio>
#include <vector>
#include <set>
using namespace std;
/*
 * http://poj.org/problem?id=1012
 * 思路:
 * 遍历所有可能的带宽,在每种带宽下计算 price
 */
struct Device {
    int mi;
    vector<int> bds;
    vector<int> prs;
};

int main() {
    int t, n, mi, bw, pr;
    cin>>t;
    while(t--) {
        cin>>n;
        set<int> bands;
        vector<Device> device;
        while(n--) {
            cin>>mi;
            Device d;
            d.mi = mi;
            while(mi--) {
                cin>>bw>>pr;
                bands.insert(bw);
                d.bds.push_back(bw);
                d.prs.push_back(pr);
            }
            device.push_back(d);
        }
        double bp = 0;
        bool finished;
        for(set<int>::iterator it = bands.begin(); it != bands.end(); ++it) {
            int cur_band = *it, sum = 0;

            for(int i=0; i != device.size(); i++) {
                finished = false;
                int cur_pr = 65535;
                for(int j=0; j != device[i].mi; j++) {
                    if(device[i].bds[j] >= cur_band && device[i].prs[j] < cur_pr) {
                        cur_pr = device[i].prs[j];
                        finished = true;
                    }
                }
                // 如果在某个设备上找不到比 cur_band 更大的设备
                // 说明 cur_band 不能作为最终结果
                // 则不再循环 并采用 bands 中的下一个 band
                if(!finished) break;
                else sum += cur_pr;
            }

            if(!finished) continue;
            if(bp < 1.0 * cur_band / sum) bp = 1.0 * cur_band / sum;
        }

        printf("%.3f\n", bp);
    }
    return 0;
}

POJ 1014

2012 May-04

这道题让我还挺自豪的,因为我自己独立的把它写出来了(虽然经历了 timeover wronganswer),而且后来搜索网上的一些解答,貌似和他们的想法都不是很像的样子。而且,通过的数据貌似还挺好 0ms

Problem: 1014		User: yisnesia
Memory: 720K		Time: 0MS
Language: G++		Result: Accepted

这让我挺欣慰的。看到网上很多人都说到了 dp 的方法,我这里好像没有用唉,我用的差不多就是基本的搜索算法,然后用到了一个比较省时的启发:从 6->1 逆序搜索,并且一次性取尽量多这个价值的大理石 (如果一次仅仅取一个会超时的哦)。

#include <iostream>
#include <cstdio>

/*
 * http://poj.org/problem?id=1014
 *
 * 这道题我把它认为是搜索题
 * 要搜索的内容是 两个大小一样的分组
 * 那么我只需要找到一个大小恰好为总和一半的分组就达到目的了
 *
 * @author: aisensiy(http://weibo.com/alistapart)
 */
using namespace std;

int marbles[6];

bool search(int cur, int half) {
    // 这里引入了贪婪的思想,要从大的开始找,并且一次取尽量多
    for(int i=5; i>=0; i--) {
        if(!marbles[i]) continue;
        // 尽量多就是满足 或者 取尽
        int num = (half-cur) / (i+1) > marbles[i] ? marbles[i] : (half-cur) / (i+1);
        if(num == 0) continue;
        marbles[i] -= num;
        if(cur + (i+1) * num == half) return true;
        else if(search(cur + (i+1) * num, half)) return true;
        marbles[i] += num;
    }
    return false;
}

int main() {
    int counter=1, sum=0;
    while(1) {
        for(int i=0; i<6; i++) {
            cin>>marbles[i];
            sum += (i+1) * marbles[i];
        }
        if(!sum) break;
        // 这里有一个小 trick,如果总和本来就是计数
        // 则一定不能分成一样的两堆
        if(sum % 2 != 0 || !search(0, sum/2)) {
            printf("Collection #%d:\n"
               "%s\n\n", counter, "Can't be divided.");
        } else {
            printf("Collection #%d:\n"
                   "%s\n\n", counter, "Can be divided.");
        }

        counter++;
        sum = 0;
    }
    return 0;
}