对象
身在成都小微企业,前两天面试深圳老牌金蝶公司。对我这个荒废了三年光影的人来说,怎一个跨度之大了得?作为人我生第一次面试的,整个面试过程,只能用诡异来形容这次感受。而结尾也是迷迷糊糊中草草收场。
不是很好的开局
我我毕业就进了国企。毕业前,在我想象中,他是一个伟光正的形象。所以我抱着人生值得,未来可期的想法进去了。结果不懂事的我被暗中转了外包。之后那就是段黑暗中的日子了。一个书记负责画大饼及洗脑,一个负责催加班。主打一个职责分离,找人时方便推卸责任。而工资也是入职以来一动不动的8000多,我也因此悲观,颓废了三年,感觉受到了欺骗,怎么是这样的?世界观崩塌了。整天就是行尸走肉的上下班,没话可说,就这样略去吧。
算了,学习其他的去了
这基本是我下班后唯一的学习和值得说的地方。生活总是先使人绝望,又去点燃人的希望,又让人失望,再让人希望,反反复复,这样来训练我的认知。作为工科生的我,关于自然的科学知识和计算机知识学了一大把,但关于人的社会知识却没有学。不知道社会险恶,所以碰到点事就绷不住了。
因此我进公司几个月之后,就伙着同事,和上级闹情绪,搞串联,搞旷工。不过我们在专业的书记面前搞这些,岂不是太嫩了点?我在这期间读列宁的《帝国主义是资本注意的最高阶段》,《资本论》第一卷。还专门跑到中马库读了很多小册子,工人小说。后来又向前,读更早的卢梭的《社会契约论》。这时的我更像是在无力的臆想,精神可嘉,战斗力拉跨。但遭受打击却催发了其他的东西。读社会学怎么能不读哲学?
完败的我又跑去读哲学,《读西方哲学史》,读《理想国》,留意智者和形而上学的针锋相对的观点,现在看来都不无道理。略过中世纪,从笛卡尔的我思故我在开始了解,到后面形而上学的终点,读《纯粹理性批判》、《实践理性批判》、《判断力批判》,《精神现象学》。最后到了理性的崩塌后的三个解决方案,马克思的异化理论、尼采的超人、弗洛伊德的精神分析。把马克思和之前的哲学史串联了起来。
从马克思的《1844年经济学哲学手稿》开始又转到经济学。从古希腊的奴隶经济开始,到中世纪的庄园经济,再到近代的资本主义。从英国重商主义开始,读托马斯孟的《贸易论》《论英国本土的公共福利》,到法国重农主义,读《献给国王和王后的政治经济学》。最后到古典政治经济学,读英国威廉配第的《政治算术》、《献给英明人士》、《赋税论》、《货币略论》、法国布阿吉尔贝尔的《法国详情》、魁奈的《经济表》、亚当斯密的《国富论》、李嘉图的《政治经济学及赋税原理》、最后到了西蒙斯第的经济危机理论结束。
最后,当然就是马克思的经济理论了。这场经济学冒险也让我经济学历史串联了起来。
走了算了
我人生加起来也没读过这么多书,遇到这么多针锋相对而又各自明智的观点。正印了那句“人是万物的尺度”。我说不出具体的影响,但没有这些知识,我可能还在浑浑噩噩。
到了去年过年,几个亲戚兄弟碰到一起,顿时激起了我的心思,社会形势恶化不可逆转,公司待遇恶化不可逆转。人生没有反途,不能在这坐以待毙,死耗下去了!
于是我今年过年后就开始努力学习,没有一天荒废。争取把这三年落下的技术追回来。这样从2月份到7月份,5个月时间,每天晚上4个小时学习,周末24个小时的学习。平时和同事暗中比较,技术已经超过不少了。是时候走了,离开这个伤心之地了。他们有家庭,这份工还算能糊口,而我没家庭牵挂,只剩下点技术,这里又缺少磨练和实践,久了技术就忘了,走了。

面试以腾讯语音会议的方式开始,时长大概40分钟。听声音,是一个中老年人事,一个中青年技术。
首先是让我自我介绍,我之前总结了一段自我介绍,基本上照着念了一遍。这是我第一次面试,很紧张,感觉声音有点发抖。没有任何反映。
然后让我介绍一下平时怎么工作的,在团队中扮演的角色。我对着准备的资料,回忆着总结了一下日常工作以及面对新项目时的组织小组工作方式。没有任何反映。
接下来问“你的工作经历和项目中遇到过哪些难题”。我说了一下刚开始那段时间用XSLT生成doc文档,然后合并的经历。然后技术人员就问我假如我有20个文档,要合成四个怎么办?我蒙了,我们那是一个资料文件,要存档的,分成四部分干什么?我提出了疑惑,他还是坚持问,并说,20个文档合成4个,每5个合并成1个,最后四个怎么合并。我想了一会儿,我就说,你是想说多线程的问题吗?那就线程同步呗。不过我们一般都使用异步,而不是多线程。我感觉他沉默了一下,不知道是不是记错了。
然后最近还遇到一个内存泄漏的问题,我起先以为是托管内存泄漏,但用内存诊断工具又没发现异常。到现在也没解决,只能一段时间就重启。我在这方面也不懂太多。(这个我是真想找那个.net高级调试一线码农去问下)
他说那你说一下多线程的异步的区别。我就说异步是语法糖,会翻译成状态机,最后用线程池里面的线程执行。多线程则是创建了一个新的线程。他问异步怎么回到原来的线程?我又懵了?为什么异步要回到原来的线程?(可能他是想问同步上下文?或者是他想问一发即忘和阻塞式的调用方式?)我就说异步方法碰到耗时任务后,原线程就返回最上层的同步方法继续向下执行了啊?而异步方法中的代码会有新线程来接手(用并行堆栈工具可以观察到这个过程),为什么需要返回原来的线程?这整个异步方法都会被交给新线程。这样可以避免原线程阻塞(控制台中才需要手动配置任务返回原线程。只有更新ui才需要返回原线程,但WPF中任务会自动返回原线程,但这也不需要我们特别指定啊?)因为他始终不正面提问,我始终不知道他想问什么?这就是面试的方式吗,诡异。
他然后问我看里简历里面写你们用webform和mvc,那他们有什么优缺点?我说我们用的不多,只中间用了一年,后面就转向webapi加web项目了。他又问,那什么是前后端分离?我又懵了?这怎么回答?我想,后端写webapi,前端写html和js,js请求后端接口的数据刷新页面,标准的web开发流程,这还要回答什么?我就说不明白你的意思。我感觉他又沉默了。他到底想问什么呢?也许webform和mvc是用的服务端组件和razor,而webapi+html不用?这有什么可说的吗?难道他不是这个意思?好奇怪啊?
我看你简历写了redis,redis是怎么缓存的?我说我们主要是缓存主要用memorycache,redis还是主要用来存一些实时数据。我的话就是当时做一个签到的接口,因为短时间内访问量可能比较大,所以把redis作为消息队列,用来削峰。(他是想问有哪些数据类型吗?)
他又接着问,MVVM是什么?这下我激动了,就说MVVM的好处是增加了一个视图模型VM,可以为UI建模,如何为UI建模,这是之前建模方法中没有见过的。而UI易变,MVVM可以让UI的变化不会影响到VM和Model。(也许还应该加上VM利于测试?我一紧张就忘了,不过我们到现在都没写过正规单元测试,存疑)
他问VM如何跟UI通信,我说是通过命令command。他问具体是怎么通信,我一时间没想明白,问是不是调用和传参。他说都讲一下。然后我说调用是通过datacontext(xaml文件在访问时通过构造函数那句InitializeComponent代码被实例化了,VM可以在这个过程中实例化,也可以通过构造注入到这个对象中,因此VM是通过委托调用的。一紧张也忘了说出来)
然后他问界面的数据变化怎么通知到后端。我说是INotification这个接口(实际上叫INotifyPropertyChanged)。他问具体怎么通知的?我想,界面的属性是依赖属性,通过将数据存到统一的一个私有静态属性中,通过属性去访问这个值,所以能知道数据的访问。但是不是所有属性都是依赖属性啊。INotification接口原理没搞清楚,没回答上来。
然后人事问我,你们加班情况怎么样,我说我们是半个国企,很少加班。
面试差不多这就结束了。然后人事问我,你还有什么想问的吗?我想好多都答得不好,估计没戏了。就说没什么想问的。结束得非常急迫,半分钟不到。
至此,面试结束。
后续总结
第一个没答上来的问题是前后端分离是什么
这怪我不识庐山真面目,只缘身在此山中。基本上没有写过以前的面条代码,一直都是前端后端分开写。结果就忘了什么是前后端分离了。这里应该是要谈web的历史。最开始只有静态html文件。服务器就是去去文件,然后返回报文。到了动态网页时期,因为后端接口返回的http报文,静态网页文件也是服务器响应的http报文,所以是可以代码拼字符串,设置content-type来模拟一个静态文件的,这个时候只有后端。再后面一点有了模板引擎,razor,webform之类的,前端可以写一些HTML+js了,但还是后端代码生成网页需要的静态文件。而现在有了xhr和fetch,一般都是前端后端各写各的。以后用blazor还是要回去。
第二个没答上来的问题是wpf中INotifyPropertyChanged的原理
从写法上来看,继承INotifyPropertyChanged接口的VM具有一个事件
public event PropertyChangedEventHandler PropertyChanged;
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
然后在属性set中,触发了此事件。到这里为止还是很好理解。那么应该是UI元素绑定到这个属性时,订阅了此事件,所以才能接到通知并更新界面。UI元素有个SetBinding方法,应该是实例化xaml文件时,遇到了{Bingding}标记,这个SetBinding方法于是被调用,才建立了UI元素依赖属性和VM的CLR属性的binding关系。然后Binding会侦听实现了这个接口的对象的PropertyChanged事件。这个对象更新属性时,clr属性中set中主动触发了此事件,然后Binding侦听到此事件,更新UI元素。
如果他又追问binding监听了这个事件是怎么取到数据,并传递给UI,通知UI变化的?我还要研究一下这个问题。
这个也比较简单,因为就是事件订阅,然后通过反射取数据源的值。
internal class MyBinding
{
/// <summary>
/// 这一般是UI元素的属性
/// </summary>
public DependencyProperty dependency;
public PropertyPath path;
/// <summary>
/// 一般是UI元素
/// </summary>
public DependencyObject dependencyObject;
public object source { get; set; }

public MyBinding( DependencyObject dependencyObject, DependencyProperty dependency, object source, PropertyPath path)
{
if (source is INotifyPropertyChanged inpc)
{
inpc.PropertyChanged += new PropertyChangedEventHandler(OnPropertyChanged);
}
this.source = source;
this.dependency = dependency;
this.path = path;
this.dependencyObject = dependencyObject;
}

private void OnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
//数据源可能又多个属性被绑定,检查是不是绑定的那个属性发生了变化
if (e.PropertyName==path.Path)
{
dependencyObject.SetValue(dependency, sender.GetType().GetProperty(e.PropertyName).GetValue(sender));
}
}
}
我来测试一下
TextInfo textInfo;
public MyBindingTest()
{
InitializeComponent();
textInfo = new TextInfo() { Text = "TextInfo" };
MyBinding myBindin = new MyBinding(this.text, TextBlock.TextProperty, textInfo, new PropertyPath("Text"));
}
private void btn_Click(object sender, RoutedEventArgs e)
{
textInfo.Text = "newValue";
}
不同之处在于wpf的绑定时基于弱事件而我这里是+=。我的MyBinding类没有继承MarkupExtension,所以没法被xaml解析。但这里暂时不研究了。
第三个没答上来的问题,Redis是怎么缓存的,这问到我的知识盲区了。我下来去网上搜了一下。Redis缓存有3种模式
Cache Aside(旁路缓存):旁路模式在读取取时,优先取缓存,没有再取数据库并刷新缓存。写数据时则更新数据库,再删除旧缓存。
这个模式其实我们MemoeyCache做缓存时也是这么干的,只是忽略了写的操作,不知道这叫旁路模式,给缓存设置了过期时间就完了。这种模式缺点是存数据和删除缓存之间时间段内读的数据和实际的不一致。
Read/Write Through(读写穿透)的改进是搞了缓存中间层,代替应用访问数据库。再中间层中进行数据库读写。并且在写数据的时候,以事务的方式更新缓存和数据库,避免数据不一致。缺点是更新数据库这段时间,其他读缓存的阻塞了。
Write Behind Caching(异步缓存写入)在读写穿透的基础上取消了事务,写数据时优先更新缓存,并在之后异步的更新数据库,这样解决了写数据这段时间里,读缓存阻塞的问题。但缺点是写入数据库之前有人访问数据库的话,数据实际是不对的。
第四个没答上来的问题是webform和mvc的优缺点,说实话,我两者都没怎么用过。对webform的了解限于知道开发起来像winform,有服务端组件和视图状态。开发起来很快。缺点是WebForms倾向于生成较为笨重的HTML和ViewState,这可能会导致页面加载速度慢,而且调试复杂,不灵活。但我们实际用webform的时候都是一般事件处理程序ashx+aspx.cs后台事件+ajax突过去得了,没想过这玩意。也许我该做个demo看看了。
MVC里面我还是第一次接触到路由,这玩意确实比原来的webform那个路由方便。还有是实现了视图和模型关注点分离。可以一定程度上前后端分离。其优点对于不熟悉的人同时也是缺点。
不管怎么说,这两个框架都是表现层框架,主要精力还是集中在UI上。
面试观点
我和同学说了这事,他说我碰到的面试可能是喜欢听听你的知识面,然后挑着问的类型,掌控方在我手里。而不是那种面试官需要明确面试者掌握了哪些知识,固定提问的类型。要发散思维,展示你的知识面。
他问了你很多自学的东西,这种记得不牢固是正常的。
还有,问你工作经历那里让你继续说是感觉还好,应该继续下去。可视我总结的不够,反而说不下去了。
他问你现在的加班情况,是考虑你能不能适应。像我这种回答人家未必会考虑。
关于工资,金蝶写的是15-30K,像你这种3-5年的应该18-20K。但是我现在这家奇葩公司月薪才8900啊,就算加年终奖也才一万,我怎么敢想。而且我之前几年一直没考虑过离职,也不知道自己在人力市场上应该要多少,我说不出口。
追加
前后端分离是什么意思
看了@过错的评论,我上网去搜了一圈。总结出这样一段话。
职责分离并非一个具体的设计模式,而是一种设计原则
职责分离(Separation of Concerns,SoC)虽然不是严格意义上的设计模式,但它是软件工程中的一种重要设计原则或思想。
它指的是将一个软件系统分解为不同的部分,每个部分解决特定的任务或关注点,从而降低系统的复杂性并提高可维护性。
具体来说,职责分离强调以下几点
单一职责原则(Single Responsibility Principle,SRP):一个类或模块应该专注于实现单一的功能或职责。这样做不仅使得代码更加清晰和易于理解,而且在需要修改时也可以降低影响范围,减少引入错误的可能性。
职责分离的实现可以依赖于多种设计模式,例如:
MVC(Model-View-Controller):将应用程序分为模型(数据和业务逻辑)、视图(用户界面)和控制器(处理用户输入和调度任务),实现了职责分离和代码重用。
MVVM(Model-View-ViewModel):在WPF和其他现代UI框架中使用的一种模式,通过数据绑定将视图(View)与视图模型(ViewModel)分离,使得UI设计和业务逻辑分开。(这确实和@过错所说不差)
前后端分离看起来是符合单一职责原则的软件设计