posts - 207,  comments - 550,  trackbacks - 9
公告
  置顶随笔
摘要: 高级软件工程师/技术合伙人职位描述:承担互联网应用及产品开发工作,主要编程语言是Python薪酬待遇:月薪(10000至18000元,具体依据个人能力与责任)、股权、五险一金职位要求:1. 有扎实的编程功底,热爱编程2. 三年以上Python或Ruby的web开发经验3. 熟练掌握web前端技能,包括HTML/CSS/Javascript4. 熟悉互联网产品和服务的开发过程5. 乐于分享,善于沟通,主动性强,且具有强烈的敬业精神和责任心优先条件:1. 热爱技术,梦想借助技术创造商业价值2. 对开源软件、敏捷开发方法有特别兴趣3.有零售业务系统开发经验者尤佳工作地点:北京有兴趣请将简历发至:er阅读全文
posted @ 2012-08-21 22:17 taowen 阅读(351) 评论(0) 编辑
  2012年10月15日

项目地址:http://github.com/honovation/veil

做为框架:改不改得动是检验一切架构的唯一标准

我们认为框架不仅仅是为了节省开发人员的时间,能够让你五分钟写一个博客系统。更重要的是给应用程序的逻辑提供一个组织的方式。因为我们知道,软件开发就是管理复杂度的艺术。管理好软件业务逻辑的复杂度,就需要代码有一个良好的组织。这么多年来,人们总结出来的金玉良言就是“高内聚,低耦合”的模块化设计方式。Veil框架仅仅是我们对于践行模块化设计的一个努力和尝试。

模块化设计并不是一个新概念。甚至C的module也可以称自己为模块化。Veil的特点在于彻底的模块化,比如有的时候我们会把东西放到不同的地方

  • 安装程序如果是集中管理的,那么每个模块的一部分实现还是要外泄到安装程序里
  • 运行脚本如果时集中管理的,那么集中的运行脚本还需要知道每个模块是如何运行的
  • 代码如果是分层管理的,每个模块实现的功能需要跨越多个层次,被割裂在几个分开的目录内
  • 页面和代码如果不是搁一块的,那么模块还是要分为代码和页面两块
  • javascript和css如果是单独存放的,那么模块的改动还要不停地在相关的javascript和页面之间跳来跳去

而有的时候我们又不把东西分开来

  • 使用相同表的方法和实现哪怕是为了完成不同功能,我们也会把这些代码放到同一个“Model”,不管它已经有多大了
  • 同一层的代码可以肆无忌惮的彼此引用,哪怕有的时cms有的是交易代码,风马牛不相及
  • 调用web service等外部服务的代码散落到代码的各个角落,可以随意引用

所以Veil从某种意义上来说是对传统”秩序“的反思。我们把很多应该放在一起的东西出于种种困难没有能够放在一起,并且已经视之为理所当然。而有些完全不该放在一起的东西,打着”分层“,”OO“等旗号被堆砌到了一起,使得代码庞杂而难以管理。Veil无视我们已经习惯的秩序,单纯地从”高内聚,低耦合“的角度出发,来评判什么应该被放在一起,而什么又不应该放在一起。

最终的目标不是为了OO,不是为了达成什么架构上的理想,甚至实现“高内聚,低耦合”也不能称之为目标。最终一个框架,以及其背后的架构设计思想的检验标准只能是响应变化的能力。也就是说,同样堆砌功能,用架构A和架构B当然都可以实现。而实现的质量除了静态的检验(功能性需求和非功能性需求是否满足),更重要的时动态的检验,就是“动”的能力。当我们需要对功能进行增删,把模块的职责互相移动的时候,代码能不能改,怕不怕改乱来,才是真正体现功力的地方。没有任何设计能够在一开时预见所有的需有,好的架构就是要让设计需要改动的时候,改得动不怕改。而在我们看来,要实现这一目标就是一个很简单的原则,就是要分而治之,同时保证被分治的部分之间保持“高内聚,低耦合”的关系。在Veil的设计过程中,就是秉着这样的目标,把模块化追求到极致。至于是否能够帮到你,是否真的能够达到前面所说的帮助你提高响应需求变化的能力,还有待实际使用中的检验。

做为库:出来混迟早是要还的

框架与应用的关系是,框架提供骨骼(所谓架构),应用提供肉(所谓业务逻辑)。但是不管是什么框架,最终还是会提供一些库来给以用使用,作者总是会期待能够把“公共”的“重复性“的劳动帮助应用完成了。而这种简便性,往往成了一个框架宣传时候的所谓亮点。但是我们知道物质是守恒的,代码的复杂度也是守恒的。实现同样的功能,不是应用程序多写一点代码,就是使用的库的多实现一些功能。从长远的角度来看,出来混迟早是要还的。所有库帮应用实现的功能,最终仍然是应用的一部分,从依赖的角度来看,你使用的语言的运行时(比如python的标准库),再下面的c运行时环境,操作系统的api,硬件驱动,乃至cpu指令都是你应用的一部分。一旦一个功能出错了,它可能是cpu出错了,也可能是你代码写错了,也可能是从你的代码到cpu中的每一个环节。

既然认识到库也是应用的一部分,那么我们就需要思考什么时候需要用库,我们需要什么样的库。Veil自身需要提供什么样的库api给应用使用。我们认为影响库的设计与使用取舍有三条:

  • 问题域本身的复杂度。 如果问题本身过于复杂,无法让库的用户安心使用库提供的api,那么这样的场合可能就不适合封装成库。比如说SQL就一个很复杂的问题域,经过ORM的封装,它事实上重新发明了一种查询语言。带来的结果是,做为ORM的用户,不但要了解ORM封装之后提供的新的查询语言,还要知道SQL(事关执行效率),而且还要知道ORM封装后的查询语言与SQL之间的翻译关系(要不然我怎么让在性能出问题的时候,让ORM翻译出想要的SQL呢)。
  • 库自身api设计是否良好,有没有泄漏内部实现。 如果问题本身不复杂,比如计算一个字符串的md5值之类的。那么就只需要api设计良好,一般用户都不用去关心起内部实现了。
  • 库的代码质量。 但是有的时候我们还是被迫去了解一些库的内部实现。比如曾经用过的一版.net的hibernate,其处理并发情况下的lazy load list的时候,从连接池有取到不同连接的问题。在彻底检查了自己的代码之后悲惨地发现是库的代码有bug。

对于以上三条,无论是哪条有问题,都会让我们进入一个还债的状态,以十倍地时间去了解我们不甚了解而又往往过于复杂的代码。因为库面对的用户是千千万万的,解决的问题也是形形色色的。比如说linux的进程调度有林林总总的实现,满足不同计算场合的要求。如果哪天我们被迫需要知道我们的进程是如何被linux调度的,那需要耗费的时间可想而知。即便是库解决的是一个明确而又小的问题,设计有良好的api,而且自身代码也不出什么问题,我们仍然会有可能遇到,问题可能稍微发生了一些变化,库不完全能解决我们的问题,而又不失完全不能解决的时候。遇到这样的场合,不少人可能都会选择与修改使用的库,要么fork,要么提交回去。

总之,就是那句话,出来混迟早是要还的。Veil的原则也很直白,就是简单粗暴。如果要封装,也尽量处理明确而又小的问题,api不泄漏实现,同时要保证质量。不要在应用的代码和最终实现之间插入太多的magic。

posted @ 2012-10-15 23:30 taowen 阅读(1378) 评论(1) 编辑
  2012年8月21日
高级软件工程师/技术合伙人
 
职位描述:承担互联网应用及产品开发工作,主要编程语言是Python
 
薪酬待遇:月薪(10000至18000元,具体依据个人能力与责任)、股权、五险一金
 
职位要求:
1. 有扎实的编程功底,热爱编程
2. 三年以上Python或Ruby的web开发经验
3. 熟练掌握web前端技能,包括HTML/CSS/Javascript
4. 熟悉互联网产品和服务的开发过程
5. 乐于分享,善于沟通,主动性强,且具有强烈的敬业精神和责任心
 
优先条件:
1. 热爱技术,梦想借助技术创造商业价值
2. 对开源软件、敏捷开发方法有特别兴趣
3. 有零售业务系统开发经验者尤佳
 
工作地点:北京
 
有兴趣请将简历发至eric@honovation.com(请注明信息来源、申请职位)。
 
Honovation是一家创业公司,致力于通过信息技术推动零售业的革新,实现基于精细化运营和消费者驱动的多渠道创新型零售的愿景。公司核心技术是实时多来源海量数据采集与分析,拥有某垂直行业领先的专业零售商资源(规模前四、持续盈利、盈利状况业内最佳),有门槛,难复制,已有稳定收入来源,不依赖风投,可滚动发展。公司创始人团队都来自著名高科技公司,拥有资深的技术背景和丰富的产品运营经验。
 
Honovation运作已有近两年了,经历了“研发产品但因缺乏大客户销售能力受挫”的一期,度过了“做项目求生存,找机会图发展”的二期,现在处于“找好了机会、握有了资源、专注快速发展”的三期。为此,诚挚招募第一批员工和我们一起创业,为自己和家人更好的生活而打拼,为社会的进步而努力。
 
我们推崇小而精的全功能团队,热爱技术,但更看重借助技术实现业务的创新与价值。在我们这里,软件工程师不只是编程,而是解决业务问题,负责从分析、设计、编码、测试、部署到运维的端到端的商业价值交付。当然,你也不是一个人在战斗,团队会为你提供帮助和支持。 
 
如果你有梦想、创业精神、不甘平庸,对互联网技术以及利用信息技术带动传统行业的产业升级有强烈的兴趣和爱好,我们热忱欢迎你加入我们的核心团队,共图发展,共创未来,共享权益。
posted @ 2012-08-21 22:17 taowen 阅读(351) 评论(0) 编辑
  2012年3月24日

第一轮

需求如下:

1、Excel中的表格能够在Word中显示出来。

2、Exce中的表格l改了之后,Word的表格中的内容要能够更新。

需要支持的平台是Mac下的Office 2011。

实现是:

在Excel中选中需要的表格区域,Copy。

在Word中Paste Special=>Paste Link=>HTML Format。

在Excel中改动一下表格内容,Word立马就会更新(理论上)。

但是有的时候它就是不同步更新。这个时候在菜单中选择Edit=>Links...=>选中Link=>Update Now。

是不是很简单?Office其实很强大的。

第二轮

需求:Excel和Word发送给别人之后,Excel和Word的表格应该还能够保持同步。

实现是:

因为Word是以绝对路径存储Link的,所以在接到了别人发来的Word和Excel之后,要在Word中手工更新一下Excel文件的位置。

在菜单中选择Edit=>Links...=>选中Link=>Change Source

第三轮

需求:Excel对表格的样式调整,不应该破坏Word对表格的排版。

实现是:

用Paste Link方式粘贴过来的内容,不但内容过来了,样式也过来了。样式过来了也就罢了,样式还阴魂不散。只要Excel那边调整了单元格大小,Word这边一同步样式就全毁了。

这个问题没有办法通过用户界面解决,只能通过VBA解决。参考:http://mac2.microsoft.com/vb/1033/Word/html/womthAddFieldsObj.htm。

设置PreserveFormatting为True,就可以保持Word这边的样式了。

第四轮

需求如下:

1、Excel中的图表能够在Word中显示出来。

2、Exce中的图表l改了之后,Word的图表中的内容要能够更新。

实现是:

在Excel中选中图表,Copy。在Word中Paste。搞定。

在Excel中改动了数之后,Word这边就会更新。但是如果Excel那边改的是图表的样式,Word这边则不会。

原因是Word这边其实是按照Excel的图表生成了一个独立的新的图表,但是数据源是同一个Excel文件中的同一块单元格。

如果没有自动更新,用Edit=>Links..=>选中Link=>Update Now

第五轮

需求是:Excel和Word发送给别人之后,Excel和Word的图表应该还能够保持同步。

实现是:

尝试一:

理论上来说,可以炮制前法,用Change Source的方式来解决绝对路径变化带来的问题。但是这个方法对图表来说不管用,至少Mac下的Office 2011不行,Windows下的没有试验过。用VBA来Change Source也是一样,仍然不行。

尝试二:

除了普通的Paste,不是还有Paste Link嘛。用Paste Link的话,应该就可以使用Change Source了。但是这种方式的问题是图表在Word这边就是图片了,而且分辨率很低,这样就影响了最终的输出质量了。

尝试三:

重新粘贴一遍嘛。用alt文字记录原始来源,用VBA重新粘贴一遍。暴力方法,当然是可行的。

尝试四:

让绝对路径不变不就行了么?用 hdiutil create test.dmg -megabytes 100 -ov -type SPARSEBUNDLE -fs HFS+J -volname test 生成一个dmg文件,然后把Excel和Word文档放到Mount出来的Drive里,这样绝对路径所有人都一样了。

posted @ 2012-03-24 12:23 taowen 阅读(1495) 评论(0) 编辑
  2012年3月11日
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.management.ManagementFactory;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;

import static java.nio.file.StandardWatchEventKinds.*;

public class WatchDog implements Launcher {

private static Logger LOGGER = LoggerFactory.getLogger(WatchDog.class);
public static final String ENV_KEY_TARGET = "WATCH_DOG_TARGET";
private final Launcher launcher;

public WatchDog(Launcher launcher) {
this.launcher = launcher;
}

@Override
public void launch(String[] args) {
if (!startWatchDog()) {
launcher.launch(args);
}
}

private static boolean startWatchDog() {
String target = System.getenv(ENV_KEY_TARGET);
if (null == target) {
LOGGER.debug("Do not have target for watch dog, launch directly");
return false;
}
LOGGER.debug("Watch dog is watching " + target + " ...");
watch(target);
return true;
}

private static void watch(String target) {
try {
int thisProcessId = Integer.valueOf(ManagementFactory.getRuntimeMXBean().getName().split("@")[0]);
String thisProcessCommand = getProcessCommand(thisProcessId);
Process subProcess = launchWithoutWatchDog(thisProcessCommand);
final WatchService watchService = FileSystems.getDefault().newWatchService();
try {
Path targetPath = Paths.get(target);
Files.walkFileTree(targetPath, new SimpleFileVisitor<Path>(){
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
dir.register(watchService, ENTRY_MODIFY, ENTRY_DELETE, ENTRY_CREATE);
return super.preVisitDirectory(dir, attrs);
}
});
while (true) {
WatchKey key = watchService.take();
key.pollEvents();
key.reset();
while (null != (key = watchService.poll())) {
key.pollEvents();
key.reset();
}
subProcess.destroy();
subProcess = launchWithoutWatchDog(thisProcessCommand);
}
} finally {
watchService.close();
}
} catch (Throwable e) {
throw new RuntimeException("Failed to watch " + target, e);
}
}

private static String getProcessCommand(int processId) {
String command = "pargs -l " + processId;
try {
Process process = Runtime.getRuntime().exec(command);
process.waitFor();
return readAll(process.getInputStream());
} catch (Throwable e) {
throw new RuntimeException("Failed to execute " + command, e);
}
}

private static String readAll(InputStream inputStream) throws IOException {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder result = new StringBuilder();
String line;
while (null != (line = reader.readLine())) {
result.append(line);
}
return result.toString();
} finally {
inputStream.close();
}
}

private static Process launchWithoutWatchDog(String command) {
try {
LOGGER.info("Launch: " + command);
ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.command(command.replace("'", "").split(" "));
processBuilder.environment().remove(ENV_KEY_TARGET);
processBuilder.inheritIO();
return processBuilder.start();
} catch (Throwable e) {
throw new RuntimeException("Failed to launch without watch dog: " + command, e);
}
}
}
public interface Launcher {

public void launch(String[] args);
}


五项关键技术:

  1. 代码改动的时候自动更新class,这个部分交由IDE来完成。
  2. 获取当前Java进程的启动命令行
    1. 获取当前进程的PID,由JMX的api完成
    2. 由PID获得命令行参数,这个部分由solaris的pargs完成,其他操作系统也有类似命令
  3. 探测文件夹内容的改动,由Java7的WatchServer完成
  4. 在Java进程内,启动另外一个Java进程。由Java7的ProcessBuilder完成。注意inheritIO这个太方便了。
  5. 同一个main函数,两种执行状态(监控和非监控)。由传入不同的环境变量完成。如果没有WATCH_DOG_TARGET那么就执行真正的main,如果有则把当前进程作为监控进程。
posted @ 2012-03-11 01:24 taowen 阅读(1134) 评论(0) 编辑
  2012年2月28日

Friendbuy是一家互联网创业公司。产品的源代码是托管在GITHUB上的。在EC2上有三套环境:生产环境,测试环境和持续集成环境。基本上每天都有大量的代码被提交,测试和部署。一年多的磨合下来,逐渐理顺了GIT的使用流程。但是,最开始并不是这样的,所有的开发人员都没有使用过GIT,基本上都是SVN的背景。

最开始的使用方式

只有一个GIT分支,就是MASTER。开发团队直接向MASTER提交新的改动,部署其实就是在生产环境下执行

git pull

开发人员的日常工作也很简单

git pull --rebase
git commit -a -m "xxxx"
git push

基本上是把Git当作SVN来使用。

生产环境不稳定

很快就出现了问题。在一次给客户的演示的过程中,掉链子了。

老板很不高兴,对于生产环境部署的质量产生了怀疑。

于是为了使得生产环境稳定,团队决定牺牲时效性,建立更加正规的流程,添加了测试环境和自动集成环境

每次提交的代码都会被自动集成环境自动进行测试

不定期的会人工部署到测试环境中测试

当测试环境测得差不多的时候才会决定部署到生产环境

另外每次部署到测试环境和产品环境的时候都会打一个标签

git tag -a xxx -m xxx

 

速度就是生命

众所周知,互联网创业公司玩的就是速度。加上了这么一套流程之后,一个feature要发布变得非常冗长。

最要命的是,网站为了尝试不同的风格,还经常全面改版。每次完整的改版都要协调各方面的资源,特别是有一个很长的UI调整过程。

问题是在网站局部改版的情况下,客户的其他特性仍然要响应,产品的线上BUG仍然要FIX。

于是就有了分支。

git branch new-retailer-site master
git checkout new-retailer-site
#change some thing
git commit -a -m "xxx"
git push origin new-retailer-site

分支用完了之后,要合并到master中

git checkout master
git merge new-retailer-site
#fix conflit
git commit -a -m "xxx"
git push

最后就可以把分支删除了

git push origin :new-retailer-site

 

千万不能搞乱master

分支在很短的时间就冲到了两位数。对于分支的管理一开始也并不在意。

直到出了这么一个事情:

开发团队一致决定new-retailer-site已经差不多了,可以“准备”发布了。然后new-retailer-site被合并到了master中。

然后在master上有开发了一些其他特性。

但是new-retailer-site的UI迟迟不能够让人满意。直到有一天开发团队被要求先把master中的“有用”的feature发布,样式改版工作延后发布。

这可难办了,所有的改动已经混杂在同一个分支中了。

最后的解决办法是用

git log

把一条条的改动给找出来,由于比对实在太麻烦了,还写了点代码来干这事

def list_commits(branch):
  commits = local('git log ' + branch + ' ^master --no-merges --format=format:%s,%H', capture=True)
  commits = commits.split('\n')
  for commit in commits:
    print('==> %s <==' % commit.split(',')[0])
    print('https://github.com/friendbuy/apps/commit/%s' % commit.split(',')[1])

然后把找出来的commit,一条条cherry pick出来

git cherry-pick <commit id>

接下来很长一段时间都是搞不清楚,到底是哪个分支是产品部署的分支。

直到某一天,团队决定以后再也不能随便把无关分支合并到master了。

正常的工作流程应该是,选定要候选发布的branch,比如说new-retailer-site

git checkout new-retailer-site
git merge master
# fix conflict
git commit -a -m "merge"
git push

然后在测试环境下

git checkout new-retailer-site
git pull

测试通过了之后,确定可以发布到产品环境了

git checkout master
git merge new-retailer-site
git push

然后把剩余的分支,逐个更新

git checkout feature-branch-1
git merge master
# ...
git checkout feature-branch-2
git merge master
# ...

总结

使用feature branch的方式,可以同时进行很多项特性的开发,并有产品的需要决定什么时候发布哪些特性。比如在圣诞节期间,基本上就没有feature branch被发布,但是开发并不会因此停滞。而且业务的优先级经常调整,经常开发到一半的分支也会被扔掉。

在使用多分支开发的时候要保持一个master分支始终对应“一定会被发布到产品环境的代码”,以master为中心保持一致的合并方向,不然分支之间乱合并就很难管理了。

目前有一个缺陷是持续集成环境只对master进行测试,理想的情况下应该建立一个staging的分支,持续集成环境也要对staging分支进行测试。

 





posted @ 2012-02-28 22:03 taowen 阅读(4660) 评论(17) 编辑
  2012年2月27日
摘要: 快捷方式:多维数据查询效率分析(1)多维数据查询效率分析(2)前面分析了在PostgreSQL和MySQL中进行多维数据查询的挑战。问题的根本在于,按行存储的数据库在行变得很大(wide table)的情况下,一旦索引无法完成所有的查询工作,就会受到行大小的影响。为了避免按行存储的缺陷,按列存储的数据库就被发明了出来。按行存储的数据库有很多,绝大部分都是要花钱的,开源的有MonetDB。和前面相同的数据量,相同的wide table的表设计,用MonetDB可以快上很多:sql>select count(contact_id) from spike2 where a1 = 7;+----阅读全文
posted @ 2012-02-27 18:23 taowen 阅读(2167) 评论(0) 编辑
  2012年2月26日
摘要: 上次我们分析了在附加属性表这样表结构设计下的PostgreSQL查询效率。由于PostgreSQL众所周知的所谓“性能”问题,所以有必要再用使用MyISAM引擎的MySQL再来实验一遍。在我们详细分析了两种常见的开源数据库之后,话题将会进一步引申到按行存储的数据库结构以及索引对于查询效率的影响。以下实验中的MySQL为MariaDB发行版本。还是从建表开始:MariaDB [veil]> show create table cc2;+-------+----------------------------------------------------------------------阅读全文
posted @ 2012-02-26 16:43 taowen 阅读(1106) 评论(1) 编辑
摘要: 有时需求需要我们把系统做成灵活的。最常见的形式是,属性不能是固定的,要用户可以自定义。这样的需求往往会在数据库中建模成一个一对多的关系。create table person {... }create table person_attribute {person_id ...attribute_name ...attribute_value ......}这样的建模在没有查询需要的时候,还是蛮不错的。但是一旦需要对扩充的属性值进行查询,速度往往惨不忍睹。曾经在新加坡做过一个电信的遗留系统的前端,其数据库的建模就是这样的。对于中间的属性表,一个简单的查询都需要join好几次,速度非常慢。好在那.阅读全文
posted @ 2012-02-26 08:33 taowen 阅读(411) 评论(0) 编辑
  2012年2月23日
摘要: 插队<<<doubanclaim64ea944f8164f0e1从计算任务的特质来看分为:1、大计算量,小数据量2、大数据量,计算相对简单3、大数据量,大计算量常见的工作负载有:1、日志分析,PB级别2、脱机分析,商业智能,重数据量,TB级别3、调查式分析,重响应速度,100GB以下4、金融计算,蒙特卡洛算法,大计算量常见的分布式计算框架:1、Hadoop,以分布式文件系统为核心的 Map reduce 框架,擅长超大数据量,高延迟,IO开销大2、GridGain,以内存数据库为核心的分布式计算框架,擅长大计算量,低延迟,IO开销小计算的结构有三种:1、SMP2、NUMA3、阅读全文
posted @ 2012-02-23 23:03 taowen 阅读(226) 评论(3) 编辑
  2010年3月10日
摘要: OK,敏捷哈。不争论什么是敏捷。我们来看一些现象,然后你来告诉我,你有没有遇到过这些问题。没人提真正的Feedback每个迭代结束之后,我都会做Showcase。但是从Showcase上收集到最多的,就是UI的问题,字体太小之类的。每个Release发布之后,项目都会部署一个试用版本。但就是不见真正的用户来“试用”,就更别提Feedback了。敏捷不是强调Feedback吗...阅读全文
posted @ 2010-03-10 10:22 taowen 阅读(2457) 评论(25) 编辑
仅列出标题  下一页