Larry Zhao

co-founder of jianshu.io, developer, game lover and soccer fan.

苹果天才吧维修记(续)

相信看过我前一篇博客《苹果天才吧维修记》的朋友,都知道我在 2013.12.06 将我的一台 Macbook Pro 送进上海浦东国金中心 Apple Store 天才吧进行维修。

当时天才吧上确定了三个建议维修的要点,都在给我的回单上写很清楚:

  • 某一个USB接口供电不足,建议更换主板。
  • 键盘上的回车键裂了,更换。
  • 抹除所有数据并且重新安装 OS X (这个是我主动提出的)。

在 2013.12.15 日晚上我接到电话说我电脑修好了,叫我去取,我还挺开心的。

第二天礼拜一,我就提前下班去苹果店拿货。天才吧等待拿货的人也不多,我前面大概有两位,但是符合预期的是我还是等了半个小时,一位天才才把我的电脑从里面拿出来给我检查。

因为后面订了饭店要跟老婆去吃饭,本来我其实是挺放心的,没准备检查,准备拿了就走的,天才说了三次「先生请检查一下您的电脑」。我想那就检查一下吧,按了一下开机,然后开始前看后看。很容易就发现了回车键没换,等到开机了又发现系统也没有重装,我当时就已经出离愤怒了。由于后面还有事情,我就赶紧催促他们收进去再做,做好再给我打电话,就算了。

等到了第二天,也就是昨天下午的时候我接到一个苹果店打来的电话,一个天才说为了要删除我电脑上之前安装的 Bootcamp 需要我的用户密码,我就给他了。要知道电话里要说清楚那么几个字母费了好大功夫,电话那头他貌似是抄下来了就准备挂电话。我想费了那么大劲终于记下来的密码还是先试一下吧,于是就催促他不要挂电话当场试验一下,没想等他电话那边打开我的电脑,他说「哦,原来已经都做好了,那不用密码了」。

就在这个时候,电话那头突然就换了一个人跟我通话,直接通知我今天去拿机器。我十分莫名其妙,就问他所有的维修都做好了么,按键什么的。他说,没有按键啊,就是换主板,我这里看不到需要维修按键啊。

接到如此莫名其妙的电话,加上昨天白跑了一趟,我真是气不打一处来。

( 此处省略一万字... )

在 escalation 到和 Genius Bar 经理通话之后,天才总管俞经理表示无论是什么问题,在苹果都是没有责任人的,一切责任不在维修工程师,不在电话通知的员工,也不在他,而在苹果,在零售店,因为 They are a team! 所以他敦促我,你有气,就还是到店解决这个问题吧,但是他幸灾乐祸的表示他5点就下班了。

我说不行,我6点20才能到。他大包大揽的说,「好,那我叫我们零售店经理某D接待你,不但你6点20来一定能见到他人,我担保让他在门口恭候你」(我反复询问,他确认意思就是在门口等我,并且这是一他承诺的,一定会做到)。我6点20到达国金 AppleStore, 可是哪里有零售店经理恭候?等进去找到他,又是把我推给另外一个经理某K来处理。

然后就又是各种已经听的耳朵出茧子的说辞,无外乎是客户太过计较:
「噢,可能是我们的工作上的疏忽。」
也许是电话里沟通的误会。」
「咱们都是来解决问题的,既然您电脑现在已经修好了,其他的就不要追究了吧。」

( 此处再次省略一万字 )

经过艰苦卓绝的斗争,两位经理终于承认在这次服务处理中苹果有过错,责任在他们,作为补偿,他们提出给我报销礼拜一白跑一趟的车费和赠送我一个 AppleCare。

最后总结一下,写以前篇的时候,我的槽点还大多在预约环节,当时我觉得只要东西送进去了,苹果的维修是靠谱的。但是这次的经历真是的让我非常失望,苹果天才吧每次维修检查的时候,天才们在那里非常耗时的狂敲 iPad 打字,写了那么冗长的维修建议,最后3项只维修了1项;从头至尾打电话来的人都不了解情况,每一个都是先说了不对的事情被我反问之后再「对不起,我不是负责的那个人,我不太熟悉情况,请给我十分钟我了解一下情况」。还有就是无可奈何的官腔和废话。

真是一次失败的维修。如果他们持续这么烂,强烈支持大家都去要 AppleCare 。

苹果天才吧维修记

想说在前面的是我并不是一个果粉,在感情上对苹果没有任何特殊想法,但是我一直觉得苹果的东西做的的确是很好,近年来一直都在用苹果的各种设备。但是就在今年,两次有幸需要进 Apple Store 到高大上的 Genius Bar 保修,但整个过程让我非常难受。让我觉得自从对服务有认知以来,这是我享受到最差的服务。我在银行排队和去医院看病也没有觉得这么痛苦,而且至少到了之后取号排队,虽然队伍可能很长,但一切是可预测的,你知道如果你越早去,那么你会越早得到服务。而去苹果天才吧,这是不可知的,它必须提前预约,但普通人是预约不到的,所有你能做的也就是去了等,但是就算去的早也不一定能被服务到。另外下面提到的见闻都发生在上海。

1. iPad / iPhone 是不可能预约到的

苹果 iPhone 维修可以当天取机的体验我觉得是非常不错的,但是预约体验绝对可以让你呕血三升,首先 iPhone/iPad 的预约是不可能预约到的。如果你的确在苹果官网经历了艰难险阻最终终于抵达了预约页面,绝大多数情况来讲,你会看到如下画面:

亦或者你会看到网站说他们出现了某描述为「error processing」的错误(今天没试出来所以没有截图,但我相信预约过的朋友肯定都见过),甚至不是一个 500 页面。这两种死法占到了大概十成。

其实早在今年上半年,苹果会专门为临时到店客户预留预约名额,所以那时候如果你起个大早,早早去排队预约,还是有可能预约到的。现在苹果已经取消了这个规则,并且改成只能预约当天维修。不过这也不能成为我们不可能预约到原因,而真实的原因是因为在我们大天朝,这事儿已经有人帮你做掉了:

我在 Apple Store 询问苹果工作人员,说有那么多黄牛把预约都抢掉了卖钱,难道你们不知道么?他们回复说:「这是人家的正当工商行为,如果您有意见请找政府相关部门处理。」

无话可说。

2. 不找黄牛交钱预约你就别想修了

如果你预约不到,就只能直接上门。对于 Mac 用户,这招还是有用的,如果你早上10点就上门,也许在那里等个一个来小时就能有空服务你了,但是 iPhone/iPad 就别想了。我去修MBP的等待的时候和同在等待的旁边一位先生聊天,他说他来修 iPhone, 网上是死活预约不到的,直接上门来了三次都是彻底没机会,现在苹果店的人在帮他联系转到授权维修点去维修,但是这样可能就会需要支付费用,同时也未必可以当天取机了。

我朋友上个礼拜去修 iPad Mini,实在无奈,只能付钱找黄牛了。

3. 预约到了你也要等

苹果的预约让我感到非常不爽的一点是,如果你预约到了,按时来了,还是要等,我今年修了一次 iPhone5,一次 Macbook Pro 每次准时按照预约时间提前5分钟到了天才吧,最后还是多等了半个小时才轮到我维修,这还叫什么预约?

4. 苹果工作人员也不知道该怎么预约

我上个周五去修 Macbook Pro,我知道需要提前预约而且只能预约当天,于是周四半夜就开始刷预约(因为我也不知道什么时候可以开始预约,就半夜开刷),11点58、59刷了两三次,12点过一点又刷了3、4次,全部结果都是错误页面(就是前面提到的 Error Processing),早上9点起床再试,还是错误页面,就只能直接出门跑到苹果店碰运气。

我在苹果店问工作人员
我:「你们到底是怎么预约的?」
果:「请到我们官网预约。」
我:「你们预约什么时候开放呢?」
果:「我们也不知道什么时候开放,请关注我们的官网预约页面。」

服了!

5. 不守时

上周五 2013.12.06 我把我的 MBP 送进天才吧,苹果承诺3天内维修结束会电话联系我去取,直到今天 2013.12.13 没有接到他们的电话。

6. 我猜苹果官网就是在阻止你预约天才吧维修

在苹果中国官网,以你要维修 Macbook Pro 为例,你需要经历点击如下:

「技术支持」 -> 「Mac」-> 「MacBook Pro」-> 「联系支持」-> 「前往 Apple Store 零售店」 -> 「预约服务」-> 「选择你要去哪家 Apple Store」-> 「点击『天才吧预约』」-> 「登录」-> 「再次选择设备 Mac」-> 「选择可用的预约时间」

作为用户体验之王的苹果,需要这样才能找到预约维修的入口,我不得不猜测他是想尽量让用户不要随便有啥事就去预约天才吧,能在网站上看文档解决的就自己解决了吧。当然作为一个企业这也很正常,这点倒是无可厚非。

炒与被炒

译自 Vibhu Norby 的博文 Firing and being fired

我始终记得,我在毕业后的第一份工作中被辞退时的每一个细节。我的经理把我和我们的工程总监带到一个会议室,然后说道:“我们给你带来了一些坏消息,我们不得不让你离开了。其实从最开始,你和公司的文化就不是最契合。我们让你离开并不是因为你不是一个好程序员,只是这里并不适合你。我很抱歉。”

听完这段话,那些刚毕业时害怕失败和无法在硅谷成为一名够格的软件工程师的恐惧感在不到三十秒内凶猛袭来;还有我过去七个月内的工作、没有做完的项目、已经建立了关系的用户、同事之间的友谊 ---- 似乎这一切都虚度了。

我没有被允许再回到我的办公室,在被护送出大楼的一路上我都在和泪水做着斗争。我在停车场呆坐了许久,考虑着是不是该到别的地方去,去做一些更容易的工作,或者找一家更大的公司也许压力会更小一点。

我始终记得,那天晚上我修改着我的简历,思索着要是那些有可能聘用我的公司发现我之前被辞退过,那我找工作的希望就算玩完了。如同所有处于我当时境地的人一样,我在心里决定,一旦他们问我,为什么我在上一家公司只做了七个月?我就说是我提出的辞职,或者可能可以说有人在挖我,也许也可以更模糊的说,“我就是离开了”。

两天后,我加入了一个我关注了很久的新公司,和三位十分杰出的人在一个车库中一起工作,解决我认为非常值得解决的问题。在这里,我得到了我需要的编程方面的指导和工具,还有让我在工作中日益精进的鼓励。我的经理们,给予我自由让我拥有创新的空间,也限制我让我保持在通往成功的轨道上。最终我们的公司被收购了,这样看起来,我在第一份工作中被炒成为至今为止发生在我身上最好的事情。

在我真正需要亲自辞退员工之前,我就已经明白,在工作中取得了多大的成就其实就是你是否正在正确的轨道上前进的明确指示。人们无法在工作中取得长期的成功,是因为他们实在太擅长他们的工作了,以至于其他事情都不值一提。而其实只要在对的公司工作,遇到对的管理者,或者热爱自己做的事情,无论起点是高是低,人们最终终归是会成功的。无论在什么方面的成功,都会滋润自身,并持续孕育出成功的果实。

我给被我辞退的工程师说的话和前文提到的一样。尽管当年我感觉我什么也没有做错,但其实我深深的明白,当年炒了我的经理告诉我的话都是真的。在我第一份工作中,我的工作是开发一个基于 ASP.NET 的站点,但其实我很想做一些和开源语言、开源框架相关的工作。在我的第一份工作中,我完成了很多死板的任务,但其实很多我自己的想法最终都没法付诸实践。在我的第一份工作中,我为一个游戏公司工作,但其实那个时候我真正感兴趣的是交流工具。回想往事,我自己没有辞职的唯一原因是当时我没有想要自己掌握自己未来的意识和勇气。所以我的工作绩效帮我说出了我的真正想法。

很多软件团队规模都很小,特别是初创企业。在这样一个小团队里是不像大公司那样有“藏身之处”的,所以预期每个新员工都能很好的融入团队是一种疯狂和不现实的想法。员工对公司的目标和产品缺乏热情会在各个方面表现出来,就算他们仅仅是无法很好的用语言表达公司的目标和产品。当你在一个并不适应的工作岗位上工作的时候,你会慢慢的开始觉得“没人听我说话”,或者你会发现你的经理好像并不满意你的工作或是工作态度。有时候经理在你身边好像变的很怪。有时候经理会要求你改正一下你的工作态度,你确实改正了之后呢他又好像不关心也没有发现。事实上,你感觉到的上级故意不聆听你的诉求、不满意你的工作或是表现的很奇怪,这些感觉都是虚幻的。我认为这是你的意识在给你发出信号,为了你日后的发展和幸福在帮你解读当前的状况:你在其他地方会干的更好。而且的确,你迟早会换地方的。

我们曾经炒掉了大约半打员工,也留下了半打员工。每一个我们炒掉的员工,都去做他们有更大热情、更加擅长亦或是他们更开心的工作了。当你辞退无法融入团队的员工的时候,留下的团队成员会有更强的凝聚力和自我认同感,这对整个团队而言获益匪浅。作为被炒也炒过别人的人,我觉得小公司现在炒人炒的不够多,初创团队的员工工作也换的不够勤。任何程度的面试都没有办法真正考察,即将加入公司的员工内心深处对于公司目标的热情,也无法评估他们的实际工作技能,在这两方面做假都太简单了。

回头来看,我希望我的第一家公司在一察觉到我无法融入团队的时候就把我炒掉,而不是让我又做了几个月。如果员工和企业文化无法契合,在前两个月,有时候最初两个礼拜,甚至可能在入职的第一天就会显现出来。面对炒掉一个员工可以会拖上几个月甚至几年,做出决定让这位错误的员工留下继续工作很容易,然而,早点分手对于公司和员工来说都是更好的选择。

MALESKINE - the mood of writing.

MALESKINE is an online notebook project my team currently working on. We focus on making an application which concentrates on writing. We are about to open the application to public soon, and now we take "Notify me" registrations.

We like to hear as more comments as possible, you could comment here, or via twitter: @larryzhao, @maleskine, here's an introduction:

Simple is nice.

We provide only the basic necessary features of a notebook instead of making you confused with the menu items and toolbars with functionalities which might never be used.

Writing mode, Writing mood

With the redundancies unloaded, we find out a simple & clean writing area without any interference always brings us the mood of writing.

Markdown supported

MALESKINE supports writing in markdown, we also provide a preview mode for it.

Note on upgrading to Mongoid 3

We've been using Mongoid 2.4 for one of our projects for quite some times and it has also gone production in a small group for like two month. It works well and I really love this ODM. Thanks to Durran.

Mongoid 3 has been out for like months. Although I have been wanting to try it out, especially when I looked at all the people discussing about it in the Email group, I didn't find any time to do the upgrade in the last two months. Just yesterday I upgraded to Mongoid 3, and I think the following things are worth noting down.

  • If you are using devise for the user management, you need to clear all the cookies, since Mongoid now uses Moped::BSON::ObjectId instead of BSON::ObjectId.

  • Remove setting of Mongoid.add_language. It is removed since custom application exceptions in various languages has been removed.

  • Remove setting of config.mongoid.logger= in your application.rb and replace it with Mongoid.logger= in your Mongoid initializer or some place else equally, since the logger setting is moved to the top level.

  • Look for all places which uses store_in syntax, it has been changed to a more complex one which provides much more complete features, check it out here: Mongoid: Persistence

  • Remember to change all BSON::ObjectId to Moped::BSON::ObjectId

  • About sort: syntax of Criteria#order_by has changed a little bit, Posts.all.order_by(:name, :desc) which worked in 2.4 will throw you an exception of NoMethodError (undefined method '__sort_option__' for :accurate_created_at:Symbol): in Mongoid 3.0. But you could change to Posts.all.order_by({:name, :desc}) or Posts.all.desc(:name). I liked the latter one a lot.

  • Criteria#count(true) was a way to tell mongoid to count with respect to limit inside the queries in Mongoid 2. Now it's removed since it would cause an additional call to DB, which is unnecessary as the result is already in your memory. You could use Post.all.limit(100).to_a.count.

That's all I've done in my upgrade to Mongoid 3. It's not all you might run into when you upgrade, so definitely remember to check out this one: Mongoid Upgrade

Bundler::GemNotFound deploying with capistrano and rvm to a jruby enviroment

Recently I get this problem when deploying my project with capistrano to a jruby envrioment.

It raises the error Bundler::GemNotFound when it comes to the task deploy:assets:precompile

* executing `deploy:assets:precompile'
  * executing "cd /opt/app/deploy/entercamp/releases/20120627163015 && /usr/local/rvm/rubies/jruby-1.6.7/bin/jruby --1.9 -S bundle exec rake RAILS_ENV=staging RAILS_GROUPS=assets assets:precompile"
    servers: ["chicago"]
    [chicago] executing command
 ** [out :: chicago] Bundler::GemNotFound: Could not find rake-0.9.2.2 in any of the sources
 ** [out :: chicago] 
 ** [out :: chicago] materialize at /usr/local/rvm/gems/jruby-1.6.7@global/gems/bundler-1.1.4/lib/bundler/spec_set.rb:90
 ** [out :: chicago] 
 ** [out :: chicago] map! at org/jruby/RubyArray.java:2371
 ** [out :: chicago] 
 ** [out :: chicago] materialize at /usr/local/rvm/gems/jruby-1.6.7@global/gems/bundler-1.1.4/lib/bundler/spec_set.rb:83
 ** [out :: chicago] 
 ** [out :: chicago] specs at /usr/local/rvm/gems/jruby-1.6.7@global/gems/bundler-1.1.4/lib/bundler/definition.rb:127
 ** [out :: chicago] 
 ** [out :: chicago] specs_for at /usr/local/rvm/gems/jruby-1.6.7@global/gems/bundler-1.1.4/lib/bundler/definition.rb:172
 ** [out :: chicago] 
 ** [out :: chicago] requested_specs at /usr/local/rvm/gems/jruby-1.6.7@global/gems/bundler-1.1.4/lib/bundler/definition.rb:161
 ** [out :: chicago] 
 ** [out :: chicago] requested_specs at /usr/local/rvm/gems/jruby-1.6.7@global/gems/bundler-1.1.4/lib/bundler/environment.rb:23
 ** [out :: chicago] 
 ** [out :: chicago] setup at /usr/local/rvm/gems/jruby-1.6.7@global/gems/bundler-1.1.4/lib/bundler/runtime.rb:11
 ** [out :: chicago] 
 ** [out :: chicago] setup at /usr/local/rvm/gems/jruby-1.6.7@global/gems/bundler-1.1.4/lib/bundler.rb:107
 ** [out :: chicago] 
 ** [out :: chicago] (root) at /usr/local/rvm/gems/jruby-1.6
 ** [out :: chicago] .7@global/gems/bundler-1.1.4/lib/bundler/setup.rb:17
 ** [out :: chicago] 
    command finished in 15575ms

But actually the gem is there, correctly installed and if you go to the server and run the command manually you will find it's actually working.

This is a annoy problem. Yes, I could choose to run the assests:precompile and other stuff on manually on the server. But it would break the automation of capistrano and also would give downtime to the application so I decided to figure this out yesterday.

After a few experiments and with the help of Michal from rvm-capistrano project, it turns out that it's because my jruby on server was compiled in 1.8 mode, and my project is running on 1.9. So when running the command, it is told to switch to 1.9 using --1.9 argument. And it could not find those gems.

The Solution
I need to fix it with reinstalling my jruby on the server with 1.9 as default use the command: rvm install jruby-1.6.7.2 --1.9 or if you already installed, you could use rvm reinstall jruby-1.6.7.2 --1.9

You could find the whole story at: rvm-capistrano-issue-20 and I also filed an issue to jruby team: jruby-issue-219

Hope it helps.

My Meals

These pictures were taken like a month ago. They were my breakfast, lunch and supper.



Avoiding space between HTML elements generated by Rails erb template

I discovered this when I was trying to render a bunch of messages to response to an ajax call. The following is my erb template at first, the partial message_entry contains a li element which holds the message:

<% @messages.each do |m| %>
    <%= render 'messages/message_entry', :message => m %>
<% end %>

And I found that the output contains spaces between each li message. In the ajax handler I am using $(data).length to get the count of the message, but with the spaces geneerate between each li, I always get n/2 elements more.

After quite a number of fails with trying this <% -%> kind of notation, I finally found that to avoid the spaces, you just need to write in one line. The following fixes my problem:

<% @messages.each do |m| %><%= render 'messages/message_entry', :message => m %><% end %>

Hope it helps.

arguments.callee.caller bug in Internet Explorer 9

Just found an IE9 bug with my colleague these days, it’s about how to get the caller of the function and it’s confirmed by Microsoft Internet Explorer team.
In older days, arguments.callee.caller is often used for retrieving the caller of the function. It works perfect in IE8 and Chrome. But recently I found it doesn’t work in IE9.
Considering the following code piece:

function func1(flag){
   if(flag){
      alert("Caller is here!");
   }else {
      func2();
   }
}

function func2(){
   arguments.callee.caller(true);
}

If I trigger func1(false) on my page, in Firefox/Chrome/IE8, the alert box will come out successfully as expected. But in IE9, you will get the following error in the console:

Then we consulted Internet Explorer Team of Microsoft about this. They confirmed that this is a little bug of IE9, and also they suggested that it would be corrected to code like this:

  function func1(flag){
   if(flag){
      alert("Caller is here!");
   }else {
      func2();
   }
}

function func2(){
   var callerFunc = arguments.callee.caller;
   callerFunc(true);
}

Furthermore, in MDN, Function.caller is proposed to be used in the case of arguments.callee.caller, but still, in IE9 it’s not working.

Hope this post will help you when you have this rare problem. My IE9 version is: 9.0.8112.16421
and here is the test html: caller.html

Lazy-load problem in Castle activerecord

Castle activerecord is from castle team. I think most of you will find it quite a nice ORM framework in C# world, despite it’s documents are not well organized.

During using Castle activerecord, there is a very buggy problem related to lazy load. And I found that there’s plenty of people who’s suffering from it.

Problem:
The problem is, if you mark up a relationship (or domain model) as lazy like following:

Public class Category{

...

[HasMany(typeof(Post),Table="Posts", ColumnKey="blogid", Lazy=true)]
public IList Posts
    {
         get { return posts; }
         set { posts = value; }
    }
}

And when you query the list of Posts from a Category instance like:
IList posts = category.Posts;

then sometimes you will get an error like:

failed to lazily initialize a collection of role: Category.Posts, no session or session was closed.

And this problem is because the object instance you are using to query is not connected to an valid session right now, so it’s impossible for the framework to query the database for the lazy part.

Castle activerecord as a framework of activerecord which hides the detail of nHibernate session, so that this seems quite abrupt to us. I am not going to details about why it happens, To know more about that I recommend two posts:

Solutions:
Actually we have more than one way to figure our way out. I am using Castle activerecord in web application so this is also web- oriented.

First, we could follow the approach suggested in Jakob’s post, to use ISession.lock every time we query a Lazy-Load property, like:

ISession session = holder.CreateSession(typeof(Category));
session.Lock(category, NHibernate.LockMode.None);
IList posts = category.Posts;
holder.ReleaseSession(session);

It works, but it’s quite a lot of code, and you also need to release the session every time, otherwise you are going to get an error.

Second, we could wrap the code querying Lazy-Load properties like below:

using(new SessionScope){
  IList posts = category.Posts;
}

This is from document from castleproject.org. It seems promising, but I haven’t tried it.

The last solution is always the best, if you read document from castleproject.org carefully, you will find they’ve already given a way to make SessionScope per request, which you could imagine that the object always finds a session to attach to.

One of the way to implement that is to add the following line to your webconfig.xml if you are using Castle activerecord 2:

<system.web>
    <httpModules>
        <add name="ar.sessionscope" 
            type="Castle.ActiveRecord.Framework.SessionScopeWebModule, Castle.ActiveRecord" />
    </httpModules>
</system.web>

And if you are using Castle activerecord 3, you’d be doing this configuration like:

<system.web>
    <httpModules>
      <add name="ARScope" type="Castle.ActiveRecord.Framework.SessionScopeWebModule, Castle.ActiveRecord.Web"/>
    </httpModules> 
</system.web>

And in official document, there’s another way to achieve it by extending the HttpApplication class, you could check it out here.