作为造轮子专业户, 自己的一些小东西不可避免地重写了一次又一次, 不过在应用层面, 我接触到的架构上的重新设计案例并不多. 几个月之前, 我结束了词焙 0.x 版本的开发, 开始重新设计并实现 1.0 版本, 是一次有意思的经历.
应用的技术周期
对于词焙 (web app) 来讲, 应用从最初概念的提出, 需求设计, 代码实现, 到未来逐渐成熟, 我想会经过如下几个阶段.
- 最初的需求实现.
这个过程中, 会有一个针对的架构设计, 这个设计或许满足最初的需求实现, 也考虑了扩展能力, 但由于思维的局限性, 通常会遗留一部分问题. - 不断补充的需求与对应的实现.
此时, 会暴露一些原有细节实现的问题, 但更棘手的是, 可能会发现原有架构难以满足进一步的改变了. - 架构的重新设计与实现.
在应用上线一段时间后, 将暴露的问题和新增的需求纳入考量, 重新设计应用架构. - 新架构实现的重构.
在实现新架构某一部分的过程中, 分析现有技术实现遇到的问题, 并进行这部分实现的重构. - 接受新需求与对应的实现的检验.
目前词焙 1.0 处在第三个阶段, 并且已经发现了一些组件设计中不完善或者不合理的地方, 会在未来进行重构.
新版词焙的架构考虑
老版本的词焙设计之初, 是需要联网使用的, 但由于用户的强烈要求, 不得不将本来为联网使用设计的简单缓存结构硬改 (其实是重写) 为支持离线和同步的结构, 很多实现非常生硬, 难以维护. 另外在 UI 这一块儿, 一开始因为进度需要, 没有做好规划, 触摸手势及相关的事件管理也没有做好统一, 这些都成了新版词焙设计时重点考虑的地方.
使用 TypeScript
前后端全面改用 TypeScript 开发, 静态类型检查与对应的自动完成很能提高开发效率.
基于 Promise (ThenFail)
刚开始接触 Promise 的时候其实有点抵触, 但后来发现上手了也挺好用的. 但本着造轮子的精神, 写了自己的 ThenFail, 使用 TypeScript 开发, 也可以更方便地添加需要的 helper.
离线数据与同步 (Syncable, 面临重写, 未来将整理开源)
除了简单的数据同步外, 词焙中需要一个可以累积的东西以储存类似每天学习时间的数据. 但考虑到未来或许还会处理更复杂的数据合并, Syncable 为这一块儿预留了相关接口. 为了进一步增加该组件的一般性, 还添加了诸如只读数据, 被动 (按需) 同步等特性.
触摸与手势 (Touch Delegate)
上一版词焙实际上也使用了类似 Touch Delegate 的做法, 但是不支持多触点, 并且缺乏终止事件传播的机制 (因为没有统一的管理), 这些是重写这部分的原因. 与此同时, 保留了之前 Gesture Identifier 的模式, 方便手势扩展.
MVC 框架 (Drop, 面临重写)
相对 ThenFail 这么个 Promise 实现来讲,,, 自己搞一套 MVC 框架确实需要点超越时代的轮子精神在里面, 但也算实现了个愿望, 因为还在 2011 年的时候就一直想写这么个框架. 考虑到开发词焙的时间不是特别充裕, 但又耐不住想要写这么个东西, 这次实现的时候调整了原有的模板语法, 力求实现简单, 同时也尽量保留原有设计的精髓 (一般性). 但感觉歪打正着, 现在反倒更喜欢这个实现简单的语法, 而一般性也得到保留 (具体可以参看 Drop 中各种 Decorator 的实现).
不过将 Drop 应用到词焙开发上之后, 还是遇到了不少问题. 其中一些问题通过添加 Decorator (修饰器) 的形式巧妙地解决了, 另一些则还有待框架本身的改进, 比如对象字面量的支持 (实现时为了解析简便, 不允许为嵌套未转义的 { }), 对表达式的解析.
服务器角色的转变和重构
目前来讲, 词焙的服务器基本就是作为数据同步中心存在的 (其实之前改离线之后就已经是这样的功能了). 同时, 新的服务器端脚本也使用了 TypeScript 和 Promise, 结构更加清晰, 实现也更加优雅.
总结
总体而言, 这次架构的重新设计自己还是比较满意的, 也产出了几个实用并且通用的组件. 我想之后会把这些经验和组件打包一下, 做成一套 web app 的解决方案. 当然, 在此之前, 还得完成具体实现的重构, 并且经得住需求的考验.