在2013即将结束的最后一个月里,我跑客户的时间时间达到了26天,作为一个技术出身的我这是非常不可思议的,在多年前我敢都不敢想! 在历史上一个月里我连续工作的天数也就27天,当然这是呆在公司办公室里,负责码代码,不会与直接客户面对面接触的(目前大多数技术人员都是如此)。这段时间的持续改变让我不得不深思!
一. 代码的价值
案例1:
最近有个项目实施难度有点大,当然我本身是技术出身,所以有时候还是比较有优势,因为可以当场为客户解决问题。我们是为一家生产型企业实施一个生产仓库管理系统,现在条件比较复杂,反正就是涉及到生产,出入库等!实施到一个出库的时候,有个功能是这样的:
客户要求公司客服人员直接查询仓库库存,然后根据订单配货,这个配货是可以合并多个订单的,做好配货单要系统生成一个拣货清单[也就是告诉仓库人员到哪里去拿货,在货架哪里,在货架第几层,是否需要拆托盘,是否要拆箱等等],仓库人员拿着清单直接到仓库将取货让后用叉车搬到代发货区域。然后发货人员拿着PDA(非常老式的那种)去扫描箱码或者托盘条码进行配货校验,最后打印发运标签(每个箱子上都要贴这种标签,每个单子估计都有几百上千箱货)。
现在有个问题就是这搬出来的货要自动分配到不同的订单中去,而且要满足订单情况。
比如A订单 产品P 需要数量 16,000 ; B 订单需要产品 P 数量为 54,000; C订单需要产品P数量 16,000; C 订单需要产品P1 2,000;
仓库拣货数量为: 16,000+54,000+16,000 (P产品) ; 2,000 (P1产品)
P产品包装: 标准包装5,000 P1产品包装: 标准包装 600
实际拣货为: 4*5000(整箱) + 10*5000(整箱)+ 2*5000(整箱) + 2000(零箱)+ 4000(零箱) --P产品; 3*600(整箱)+200(零箱)
那上面分配货品有很多种分配方式,不同的订单可以共箱(在一个箱子上贴两个标签 200 + 400 分别是不同的订单)。
至于具体情况如何,这个也不是一会能够描述清楚的,做过拣货的朋友应该清楚:
问题是我写了一段代码,自动分配拣货的,大概花了我4天左右的时间,总共代码行估计接近一千行了,代码部分如下:
int qty = detail.Qty; int fullBoxNum = packageNum != 0 ? qty / packageNum : 0; //理论整箱箱数 int sinQty = packageNum != 0 ? qty % packageNum : 0; //理论零箱数 int sinBoxNum = sinQty == 0 ? 0 : 1; //理论零箱箱数 string PO = detail.OrderNum;//PO ShipLableEntity ship = null; /** * 当拣货得到的箱子中整箱数大于0的情况: * 当拣货得到的箱子中整箱数==0的情况: 说明所有的拣货都要从零箱中去组合得到 * */ if (listFullBoxes.Count > 0) { //当理论拣货整箱箱数大于0 且 实际整箱也大于0的时候 if (fullBoxNum > 0) { //如果理论总箱数小于或等于实际总箱数则扣除实际理论箱数,并且判断是否有零箱 if (fullBoxNum <= listFullBoxes.Count) { ship = FormatShipLable(product, PO, outNum, listFullBoxes.First(), orderEntity, index, fullBoxNum, listDefault, listBills); listShipLables.Add(ship); listFullBoxes.RemoveRange(0, fullBoxNum); index++; //判断有零箱的情况,找到最适合的箱数,没有就要拆箱分组. 如果没有零箱什么都不用处理 所以不需要else if (sinBoxNum == 1) { //如果存在理论零箱的时候,先要实际零箱的箱数是否大于0且零箱的总数也大于理论零箱的数量 if (listSinBoxes.Count > 0 && listSinBoxes.Sum(a=>a.QTY)>=sinQty) { //升序排列,查找大于等于零箱数的第一个箱拆分 listSinBoxes = listSinBoxes.OrderBy(a => a.QTY).ToList(); OutBillEntity tempBill = listSinBoxes.Where(a => a.QTY >= sinQty).FirstOrDefault(); //如果存在满足上面条件的箱,则需要拆分处理 if (tempBill != null) { //如果数量刚好满足则直接生成标签 如果不满足则拆分为两个 if (tempBill.QTY == sinQty) { ship = FormatShipLable(product, PO, outNum, tempBill, orderEntity, index, 1, listDefault, listBills); listShipLables.Add(ship); listSinBoxes.Remove(tempBill); index++; } else { tempBill.QTY = tempBill.QTY - sinQty; OutBillEntity billNew = new OutBillEntity(); billNew.LairdPN = tempBill.LairdPN; billNew.CustomerPN = tempBill.CustomerPN; billNew.StackBarCode = tempBill.StackBarCode; billNew.BoxBarCode = tempBill.BoxBarCode; billNew.QTY = sinQty; ship = FormatShipLable(product, PO, outNum, billNew, orderEntity, index, 1, listDefault, listBills); listShipLables.Add(ship); if (tempBill.QTY <= 0) { listSinBoxes.Remove(tempBill); } index++; } } else { //如果不存在则需要组合得到,查询组合求和数量小于等于零箱的数量 int zuQty = 0; //计算总和 int sinIndex = 0; //满足条件之后停止的索引 for (int i = 0; i < listSinBoxes.Count; i++) { zuQty += listSinBoxes[i].QTY; if (zuQty >= sinQty) { sinIndex = i; break; } } //如果刚好满足则生成相应的标签,并且删除 if (zuQty == sinQty) { for (int i = 0; i <= sinIndex; i++) { ship = FormatShipLable(product, PO, outNum, listSinBoxes[i], orderEntity, index, 1, listDefault, listBills); listShipLables.Add(ship); index++; } listSinBoxes.RemoveRange(0, sinIndex + 1); } else { //删除之前的,最后一个拆分 int beforeQty = 0; for (int i = 0; i <= sinIndex - 1; i++) { beforeQty += listSinBoxes[i].QTY; ship = FormatShipLable(product, PO, outNum, listSinBoxes[i], orderEntity, index, 1, listDefault, listBills); listShipLables.Add(ship); index++; } tempBill = listSinBoxes[sinIndex]; tempBill.QTY = tempBill.QTY - (sinQty - beforeQty); OutBillEntity billNew = new OutBillEntity(); billNew.LairdPN = tempBill.LairdPN; billNew.CustomerPN = tempBill.CustomerPN; billNew.StackBarCode = tempBill.StackBarCode; billNew.BoxBarCode = tempBill.BoxBarCode; billNew.QTY = sinQty - beforeQty; ship = FormatShipLable(product, PO, outNum, billNew, orderEntity, index, 1, listDefault, listBills); listShipLables.Add(ship); index++; listSinBoxes.RemoveRange(0, sinIndex); } } } else { //如果实际零箱数为0 或者实际零箱数小于理论零箱数,那么就要拆整箱,因为实际整箱大于理论整箱所以拆整箱 if (listFullBoxes.Count > 0) { OutBillEntity tempBill = listFullBoxes.First(); tempBill.QTY = tempBill.QTY - sinQty; listFullBoxes.Remove(tempBill); listSinBoxes.Add(tempBill); OutBillEntity billNew = new OutBillEntity(); billNew.LairdPN = tempBill.LairdPN; billNew.CustomerPN = tempBill.CustomerPN; billNew.StackBarCode = tempBill.StackBarCode; billNew.BoxBarCode = tempBill.BoxBarCode; billNew.QTY = sinQty; ship = FormatShipLable(product, PO, outNum, billNew, orderEntity, index, 1, listDefault, listBills); listShipLables.Add(ship); index++; } } } }
以上代码可以忽略不计,跑到客户那边实施这个功能(部分上线) , 当时我感觉很牛逼,因为我作为实施人员还在写代码 而且还搞定了,公司的其他技术人员没有搞定。 到客户现场使用之后,工厂工人说这个太慢了不好用,给他们领导反馈这个他们不会使用这个东西的,完全是浪费他们时间,上报到公司之后后果各位也想得出来,被客户噼里啪啦的说了一顿说做的东西不行怎么怎么。
心理受到极大的创伤啊,自己辛辛苦苦弄出来的东西就这样一句话给抹杀了,实在是痛心。
OPEN MyCursor FETCH NEXT FROM MyCursor INTO @RowNum,@Qty WHILE @@FETCH_STATUS=0 BEGIN set @SumQty = @SumQty+@Qty IF @SumQty>=@Num + (@PackageQty * 5) BEGIN break END FETCH NEXT FROM MyCursor INTO @RowNum,@Qty END CLOSE MyCursor DEALLOCATE MyCursor--释放游标 ; WITH TempTable AS ( SELECT ROW_NUMBER() OVER (ORDER BY DCTime ASC,ID DESC) RowNumber,DCTime,ID, OrderNum,CusNum,CreateTime,LairdPN,CustomerPN,Qty,LocalNum,StackBarCode,BoxBarCode,HasStack,IsBlend,IsHalfBox,LairdNum,LairdBoxNum,IsFullStack FROM LocalProductRel WHERE LairdPN=@LairdPN AND CustomerPN=@CustomerPN AND IsLock=2 AND IsReject=2 AND IsRework=2 AND IsChange=2 AND IsOnlyCustomer=2 AND LocalNum!='XF9-9-9' AND [StorageNum]='7112' AND Qty=@PackageQty ) SELECT * FROM TempTable WHERE RowNumber<=@RowNum
回去之后又努力优化程序,大概又花了两天时间,速度提升了不少,再去客户那边操作人员满意OK,得到赞赏,心里美滋滋啊!
仓库操作人员其实不懂计算机的,只会上QQ,看新闻什么的,编程什么的就是妄想之中的事情。所以你写的代码好与坏跟他们无关。上面的代码开始我认为自己写的很好,可事实不是这样的,客户不买你的帐,你写的再好客户又看不懂对他们来说毫无意义,一不会给他们减轻工足量反而增大了,二不能给他们美感(软件上没有美女),三不会给他加工资。
第一段代码你写的再多,你花了多少天时间,其实在客户面前是毫无价值的。
第二段代码多么的丑陋,好多问题(我自己认为,当时为了解决问题没有考虑那么多的代码有雅性),但是对于客户来说非常有价值减轻了他们的工足量。
总结如下:
(1) 代码量的多少以及编写代码所花费的时间都不是软件的价值
(2) 写代码最重要的目的是解决实际问题(操作方便,减轻工作,舒适美感)
(3) 代码对软件的价值是体现软件的便捷性以及强大的功能
(4) 客户是不会买你代码量的账的,客户不懂代码也不愿意鸟你的代码是如何实现的
(5) 解决客户问题了你的代码就有价值
以上结论不代表说代码规范性不重要,代码规范性是为了实现好的软件,将软件的优势体现出来,代码本身是不具备任何价值的。
二. 服务价值和软件价值
软件是为客户服务的,这两者有必然的联系! 那软件的价值是不是就是服务的价值呢。
问题1:客户是买你的软件还是买你的服务
问题2: 除软件之外的服务,你的价值又体现在哪里
现在很多公司都很牛气,给某个公司卖了一套软件然后实施成功之后,那后面的技术服务态度就烂的.... ,以前深有体会。他们总会这样回复:" 我们的软件在其他公司都用的很好,就你们的问题特别多,你们现在的问题不属于我们的服务范围 "。当初买软件的时候到底是买的软件还是服务啊,反正现在也说不清了。现在个人认为客户买你的软件是需要你的软件服务他们公司更好的协同工作管理,而所需的服务就是在软件出现问题的时候或者软件不知道怎么使用的时候能够快速提供帮助。所谓服务其实也就是"客户哪里痒你就帮他挠哪里"。
同样一款软件很多公司都可以开发,而且开发的很不错,无论是在操作便捷性还是界面美观性上都很不错,现在这样的公司非常多。但是客户为什么要选择你为他们开发软件,难倒真的是因为你们公司其他公司技术要厉害么(这里排除价格的问题)。
这里先给各位看看一个界面的截图:
这个是打印一个施工单的部分界面截图,这个页面打印可能稍微有点复杂啊,不过我相信大部分技术公司都能够做出来.
当时我公司去实施这个项目的时候,并没有提到这个东西,打印一个施工单据随着生产施工去流转。因为在生产车间不能使用电脑,某个产品生产到那个步骤了需要知道。我们提出了一个方案就是将每一次施工的流程打印出来放到车间中去,每到工艺流程结束之后这个施工单会随着产品走动,操作人员可以看到目前进行到哪一步了。
这个功能并不是很强大,功能也不是很起眼,但是这个很重要,为什么,因为你帮助他们实际解决了生产车间的问题,生产车间的环境你以为都跟办公室一样啊。别说工人赚的钱比你码农多,他们的的确确是辛苦钱。
在这个问题上,我们除了客户要求的软件功能之外,还帮他们处理一件实际操作中的问题,客户没有想到用这种方式来解决,但是我们想到了并且帮他们实施了,我们的价值体现出来了。我们的价值就是解决客户实际生产中遇到的问题,我们是深入到他们的生产工艺流程中去了,而不只是简单的为他们开发了一套软件然后他们可以用。 很多公司认为我们为客户开发了一套软件这就是我们的价值,这话的确没错,但是你能够做到的其他公司也能够做到,但是都没有解决客户痛痒的根源。
总结:
(1) 软件的开发只能体现你的技术能力比较强
(2) 能够解决客户的痛痒问题那就是你的真正价值,这个部分在软件之内但是绝对的高于软件
(3) 客户对你的认可肯定不只是你会开发软件
(4) 服务不只是随叫随到,而是主动去帮助思考问题
三. 个人的价值
跑了好多工厂,也认识了很多很多工厂车间工作的工人,有时候我们会讨论到工资的问题,估算下来他们的平均工资也就3000-4000左右吧,这算比较不错的了(这里是只流水线上的工人,不是车间里面的技术工,技术工也有很高工资的,甚至比码农还要高)。目前我所了解到的做开发的技术人员工资毕业差不多也有四五千吧,我公司是这样的一个标准。
工厂流水工工资级别停留在了3000-4000,而技术开发人员这只是一个起步,不同的行业为什么有这么大的差别?
显而易见的是技术开发人员是脑力工作,而流水工时体力工作,这个社会大的基本形态就是这样的,脑力比体力赚钱多。
下面的观点请各位勿喷:
1. 外包的为什么总是抱怨自己学不到公司,在我的观点中外包就是码代码的,这属于脑力中的体力活,几乎很少有人会去管这个事情是不是一定要干好,你的价值就是代码量的产出量
2. 为什么公司总是不给我加薪,或者加薪那么少? 作为程序员你为公司是奉献了脑力还是体力?如果程序员能够很清楚这个我估计加薪也不会少?[之前有给公司提过50%的加薪,公司很爽快答应了,当然也有进步源于起点低的原因]
3. 我不只是码代码的程序员,很多人都是这么想但是很多人都是这么做的,到公司你就将自己定位了自己是做技术的,什么是做技术那就是写代码。
4. 如何发挥自己的价值,不是写好代码就可以了,还要让自己写好的代码转化为让客户接受并欣赏,那就是你的价值。
5. 你做一件事如果你发现今后有让你激动的地方那你就有价值了。
以前的一个同事给我说,公司那一年只有你们项目做的东西在公司算是真正盈利了的,想当初那是何等的痛苦啊,当时自己都瞧不起那样的项目,可是客户认可产生了价值,你也就有价值了,当时你解决了公司的项目问题并且让其得以持续下去(解决问题不一定只是技术问题)。听到这个热泪盈眶啊,你所有付出的都得到了认可,你是有价值的人。
本文也是杂七杂八的乱写,貌似现在做了实施事情时间也比较自由多了,关键是还有时间写文章啊,虽然写的不怎么好,希望大家勿喷!
换一种思路换一种角度思考问题可能会让你更加的透彻明白,最近自己的一些工作体会分享给还在办公室角落独自写代码的技术人员,走出来看看或许会让你的目标更加明朗!