昨天为了一个“特殊”的需要,我跟一个BTP-2200E条码打印机“鼓捣”了一下午,
竟然搞出了一个我现在最为满意的程序。
额......当我第一次知道要写那个“特殊”程序的时候,其实我是拒绝的!因为我觉得……呀……你不能叫我写马上写,第一,我要试一下,我又不想说……你接一个需求写完以后加了很多框架,那程序跑啊……很快!很短!很小!结果客户出来一定骂我,根本没有这种程序!这证明上面那个是假的……我说先要给我试一下。后来我经过也知道他们是条码的,而且没有那种三帝成分的。那……写了这下午……这下午下来之后呢……起码我写了很舒服。现在呢……每天还在写!每天还在写呢.....我还给了我业务员用!来!来!来!大家试试看!那我跟老板讲︰「写的时候就写!写完之后,这个代码就是我的代码,就不要加,再加框架上去,加到没有就是这样子!我要给客户看到,我写完之后是这样子,你们写完之后,也是这样子!
说到打印机,多少让人联想到计算机的起源,比如磁带,比如移位。这个BTP-2200E是一个仓库用的条码打印机,一条长的黑色磁带,和一个长的白色底带,从盒子一端嘀嘀嘀的出来一张卡片似的条码。打出来是类似这样的:
+----------------------+
| 货号: 16C002-2
| 颜色: 粉色 尺码: 236
| 材质: 牛皮 等级: 一型 |
| QB/T1001-2006 |
| GB/T22969-2009 |
| |||||||||||||||| |
| 16C002-2039136 |
+-----------------------+
源代码如下:
(function(global, mod) {
if (typeof exports == "object" && typeof module == "object") {
return mod(exports);
} else if (typeof define == "function" && define.amd) {
return define(["exports"], mod);
} else {
mod(global.BPLA || (global.BPLA = {}));
}
})(this, function(exports) {
var BPLA_OK = 0,
port = -1,
errno = '',
messages = {
errorLoadBplaocx: '没有成功加载BPLAOCX插件',
errorOpenPort: '打开端口错误,请检查连接或端口参数设置',
errorTimeout: '超时配置未能正确设置,请检查连接或端口参数设置',
errorPrint: '打印条形码错误'
};
// 检测到错误,中断程序
function error(m) {
errno = messages[m];
}
// 检查BPLAOCX插件加载
function checkatx() {
if (typeof BPLAOCX !== 'object' || BPLAOCX === null) {
error('errorLoadBplaocx');
}
}
// 关闭端口
function close() {
BPLAOCX.BPLACloseUSB(port);
port = -1;
return "Port closed!";
}
// 打开端口
function open() {
if ((port = BPLAOCX.BPLAOpenUSB(0, 0)) === -1) {
error('errorOpenPort');
} else if(BPLAOCX.BPLASetTimeOut(port, iWrTime, iReTime) != BPLA_OK) {
close();
error('errorTimeout');
}
}
// 程序序列器,这个工具会顺序运行操作,直到出现一个错误,序列器会终止运行,
// 并返回错误。如果成功打印,序列器会返回一个成功信息。
function sequence(/*fn1, fn2, ...*/) {
var functions = Array.prototype.slice.call(arguments),
i = 0,
fn;
while (typeof (fn = functions[i++]) === 'function') {
fn();
if (errno !== '') {
return {msg:errno, state:0};
}
}
return {msg:'成功打印条形码!', state:1};
}
function printer(method, args) {
return function () {
if (BPLAOCX[method].apply(BPLAOCX, args) !== BPLA_OK) {
error('errorPrint');
}
};
}
// 打印队列
exports.print = function (data) {
// data: {
// serial: String, 货号
// color: String, 颜色
// size: String, 尺码
// material: String, 材质
// level: String, 等级
// model: String, 型号
// qbnum: String, QB号
// gbnum: String, GB号
// hcode: String, 水平码
// vcode: String, 垂直码
// }
return sequence(
checkatx,
open,
printer('BPLASetPaperLength', [port, 550, 0]),
printer('BPLAStartArea', [port, 0, 1000, 0, 0, 0, 0]),
printer('BPLAPrintBarcode', [port, data.hcode, 150, 100, 1, 24, 80, 4, 2, "000", 0, 0]),
printer('BPLAPrintBarcode', [port, data.vcode, 860, 50, 4, 5, 80, 4, 3, "000", 0, 0]),
printer('BPLAPrintTruetype', [port, data.hcode, 210, 25, "Arial", 40, 0, 1, 0, 0, 1]),
printer('BPLAPrintTruetype', [port, "(内部使用])", 330, 70, "Arial", 25, 0, 0, 0, 0, 1]),
printer('BPLAPrintTruetype', [port, data.gbnum, 120, 200, "Arial", 35, 0, 0, 0, 0, 1]),
printer('BPLAPrintTruetype', [port, data.qbnum, 120, 260, "Arial", 35, 0, 0, 0, 0, 1]),
printer('BPLAPrintTruetype', [port, "型号: " + data.model, 460, 260, "黑体", 35, 0, 0, 0, 0, 1]),
printer('BPLAPrintTruetype', [port, "材质: " + data.material, 120, 320, "黑体", 35, 0, 0, 0, 0, 1]),
printer('BPLAPrintTruetype', [port, "等级: " + data.level, 460, 320, "黑体", 35, 0, 0, 0, 0, 1]),
printer('BPLAPrintTruetype', [port, "颜色:", 120, 380, "黑体", 35, 0, 0, 0, 0, 1]),
printer('BPLAPrintTruetype', [port, data.color, 250, 370, "黑体", 50, 0, 1, 0, 0, 1]),
printer('BPLAPrintTruetype', [port, "尺码:", 460, 380, "黑体", 35, 0, 1, 0, 0, 1]),
printer('BPLAPrintTruetype', [port, data.size, 600, 372, "黑体", 50, 0, 1, 0, 0, 1]),
printer('BPLAPrintTruetype', [port, "货号:", 120, 440, "黑体", 35, 0, 0, 0, 0, 1]),
printer('BPLAPrintTruetype', [port, data.serial, 250, 430, "黑体", 50, 0, 0, 0, 0, 1]),
printer('BPLAPrint', [port, 1, 0, 1]),
close
);
};
});
调用的方式是这样的:
HTML页面有一个打印按钮,当点击的时候,打印机会打开端口,打印传送的数据。
这个程序的主要处理程序是exports.print(data)
,输入一个配置过的数据对象,
调用sequence
序列器,运行一串操作。
BTP-2200E的开发库提供了一个BPLAOCX
模块,包含了打印机可调用的接口。每一个BPLAOCX
调用都会返回一个状态函数,-1或者是大于0的值,用来检测打印机的可用状态,这意味着,如果你不做一些处理,你的代码将会充满了大量的没有多少价值的if else
:
if (BPLAOCX.BPLASetPaperLength(port, 550, 0) < 0) {
// 一个错误
} else if (BPLAOCX.BPLAStartArea(port, 0, 1000, 0, 0, 0, 0) < 0) {
// 一个错误
} else if (BPLAOCX.BPLAPrintBarcode(port, data.hcode, 150, 100, 1, 24, 80, 4, 2, "000", 0, 0) < 0) {
...
if else
是算法逻辑所必需的,但是如果只是单纯的状态检查,而且是一大堆的时候,你就会有些崩溃了,尤其是以后需要修改内容的时候。在长长的if else
代码中查找和修改代码都是很容易产生bug,而且这些代码很难有描述性。
所以,最终,一个printer(method, args)
用来作为一个语法糖,封装了错误处理。
错误处理是通过一个全局的messages
对象拾取,一个errno
标识来访问。如果errno
是空值,就表明操作没有错误,如果errno
是非空值,就表明出现错误,操作需要终止。(这个手法其实是抄袭自C的标准库)
printer()
调用BPLAOCX
的方法,操作打印机,当出现错误的时候,就设置errno
的对应错误值。
sequence()
是一个流程处理函数,输入参数都是函数。sequence()
会顺序处理序列,调用每一个进入的函数,运行完的时候检查当前的errno
状态,出现错误的时候,立刻终止程序,并返回一个错误信息。当所有打印成功完成后,关闭打印端口,并且返回一个成功信息。
这个打印序列的操作过程:
⇀ 检测打印模块加载
⇀ 打开端口
⇀ 设置打印纸长
⇀ 调整条码参数
⇀ 调整条码参数
⇀ 调整文字参数
⇀ 调整文字参数
⇀ 调整文字参数
⇀ 调整文字参数
⇀ 调整文字参数
⇀ 调整文字参数
⇀ 调整文字参数
⇀ 调整文字参数
⇀ 调整文字参数
⇀ 调整文字参数
⇀ 调整文字参数
⇀ 调整文字参数
⇀ 调整文字参数
⇀ 开始打印
⇀ 关闭端口
而ta所对应的程序代码正是:
sequence(
checkatx,
open,
printer('BPLASetPaperLength', [port, 550, 0]),
printer('BPLAStartArea', [port, 0, 1000, 0, 0, 0, 0]),
printer('BPLAPrintBarcode', [port, data.hcode, 150, 100, 1, 24, 80, 4, 2, "000", 0, 0]),
printer('BPLAPrintBarcode', [port, data.vcode, 860, 50, 4, 5, 80, 4, 3, "000", 0, 0]),
printer('BPLAPrintTruetype', [port, data.hcode, 210, 25, "Arial", 40, 0, 1, 0, 0, 1]),
printer('BPLAPrintTruetype', [port, "(内部使用])", 330, 70, "Arial", 25, 0, 0, 0, 0, 1]),
printer('BPLAPrintTruetype', [port, data.gbnum, 120, 200, "Arial", 35, 0, 0, 0, 0, 1]),
printer('BPLAPrintTruetype', [port, data.qbnum, 120, 260, "Arial", 35, 0, 0, 0, 0, 1]),
printer('BPLAPrintTruetype', [port, "型号: " + data.model, 460, 260, "黑体", 35, 0, 0, 0, 0, 1]),
printer('BPLAPrintTruetype', [port, "材质: " + data.material, 120, 320, "黑体", 35, 0, 0, 0, 0, 1]),
printer('BPLAPrintTruetype', [port, "等级: " + data.level, 460, 320, "黑体", 35, 0, 0, 0, 0, 1]),
printer('BPLAPrintTruetype', [port, "颜色:", 120, 380, "黑体", 35, 0, 0, 0, 0, 1]),
printer('BPLAPrintTruetype', [port, data.color, 250, 370, "黑体", 50, 0, 1, 0, 0, 1]),
printer('BPLAPrintTruetype', [port, "尺码:", 460, 380, "黑体", 35, 0, 1, 0, 0, 1]),
printer('BPLAPrintTruetype', [port, data.size, 600, 372, "黑体", 50, 0, 1, 0, 0, 1]),
printer('BPLAPrintTruetype', [port, "货号:", 120, 440, "黑体", 35, 0, 0, 0, 0, 1]),
printer('BPLAPrintTruetype', [port, data.serial, 250, 430, "黑体", 50, 0, 0, 0, 0, 1]),
printer('BPLAPrint', [port, 1, 0, 1]),
close
)
在异步代码的时候,我会常常写一些sequence
方式的语法糖,而在顺序的代码中很少去这样做。然而,这次偶然的经历,让我对代码的编写有了一些新的思考。即便是顺序的代码,也许仍有一些巧妙的方法可以变得更加灵活。