写在前面

从毕业入职到现在,我所处的岗位一直是「iOS 端开发工程师」,但在具体工作内容上,除了 iOS 端本职工作以外,偶尔我也会接触到一些前端、小程序开发的场景。尤其是最近几年参主导钉钉跨五端项目之后,借项目机会我开始接触桌面端研发,现已具备一定的桌面端研发能力。
这一段走过来有困难也有收获,同时也得到了很多小伙伴的帮助,因此希望能通过自己视角来阐述一个 iOS 工程师学习桌面端研发、尤其是 Windows 研发的些许心得,为 iOS 到 Windows 这个稍显「小众」场景提供一些资料,抛砖引玉以供大家交流,同时为有类似想法的同学提供一点参考。

补充说明

因文章主要是「个人观点」,能力与经历对内容客观性可能会产生影响。因此为避免造成误解我简单一下自己的经历:我毕业之后加入阿里,一直在来往/钉钉 iOS 团队,前后做过原生开发、Web 混合开发、Weex 研发,目前在主导钉钉内 Flutter 基础能力建设与业务落地,项目代号 Dutter(DingTalk Flutter)。也就是借助 Dutter 项目我涉及到了桌面端研发领域,学习了相关知识,对这一过程经验的沉淀即是本文的主要内容。
除此以外还有两点需要说明一下:
  1. 因经验所限,本文所谓「桌面研发」更多是指「钉钉桌面研发」。钉钉桌面端团队在能力和专业度上毋容置疑,但毕竟受历史包袱所限,目前钉钉桌面研发环境有一定独特性。因此虽然我在梳理文章时尽量整理通用问题,但仍难免有部分问题是因钉钉环境导致,如有类似情况希望大家及时指正;
  2. 一般意义上的桌面端研发包含 Windows 研发、macOS 研发、Linux 研发,但在跨越复杂度和差异性上 Windows 都要远超其他两个平台。因此在文章后续内容会着重聚焦在 Windows 研发上,对于其他两平台如需要可线下交流;

桌面研发

本章节先从整体给大家简单介绍一下我对桌面端研发的理解,以及在上手研发之后的感受,以此作为后续内容展开前的铺垫。

研发现状

从九十年代开始互联网技术蓬勃发展,传统桌面端研发(C/S 架构)便逐渐「日薄西山」,Web 技术(B/S 架构)在效率以及兼容性上的优势使其飞速发展。直到最近十来年移动互联的大爆发,才让 App 应用研发模式重新走向前台,但尽管如此,目前 App 研发更多也仅仅局限在移动端研发领域。传统桌面端研发技术,更像是喧嚣闹市中偏居一隅的小圈子:圈内的技术在不断迭代发展,但是圈内与圈外交流甚少。
哪怕对于像阿里、字节这样的「大厂」,后端研发、前端研发、移动端研发这「技术三巨头」人数可达数万人,但在技术团队中具备专业桌面端研发能力人数可能只有数百人规模。造成现在这种情况也很好理解,在流量为王的移动互联网生态中,移动端的价值远远大于桌面端的价值,很多产品仅靠移动端即可达成自己的核心目标,在这种情况下建立培养一个桌面端团队并不是一个 ROI 很高的选项,哪怕有少量桌面诉求也基本由前端同学来「代劳」了。
但少并不意味着没有,尤其在流量见顶、红利期过后,大家下一步的注意力会更加专注到高质量用户之上,为更有价值的用户提供更高品质的服务 会是未来一段时间非常重要的一件事情。比如面向 toB 场景的钉钉,作为一款办公软件,我们不仅仅只是提供 Android、iOS 端应用,在桌面(Windows&macOS&Linux)同样提供有独立客户端。并且在对体验和性能的高要求下,我们又不可像部分产品一样全部押宝在 Web 技术上,我们需要通过原生技术来为客户提供更高质量的服务。因此在钉钉,我们有一个非常专业并不断壮大的桌面端研发团队,在桌面端应用与研发技术上也慢慢摸索出一条适合钉钉的发展道路。

整体感受

在上手桌面端 Windows 研发之后,从个人角度来看,颇有一种「智能车时代学习开手动挡」的感觉。
本身在 toC 端的产品中,苹果和微软各种就有着鲜明的特点:
  1. 苹果会在产品交互上精雕细琢,功能全而不繁,尽量屏蔽掉用户不用关注的细节,让用户使用时将注意力专注到产品体验上,对于用户可能高频使用的功能入口,大多能安排到合理的位置上
  2. 微软提供的产品重功能的丰富性,但交互上缺少层次感,往往产品打开首现映入眼帘的常常是一堆功能按钮,其中有些功能甚至在整个使用周期内用户都不会点击,但「它」仍然静静的呆在那里;反而有些我们需要使用的功能还需要通过文档或者教程才能找到对应入口;
这一体感在 toB/toD 的开发工具上仍然适用。在习惯了 Xcode 这种一站式开发工具之后转战 Windows 平台,尽管我们有一个非常强大的 Visual Studio,但仍需要学习在面向不同场景使用不同的工具:
  • 在开发阶段我们需要通过 Visual Studio 来编码以及调试;
  • 当出现视图问题是我们需要通过 spy++ 来调试窗口;
  • 当出现 Crash 等问题时,我们需要通过 WinDbg 来调试分析收集上来的 PDB 文件;
  • 当出现进程间访问通讯问题时,我们需要借助 Procexp 来辅助分析潜在问题;
  • 当需要诊断一些关键操作时,我们需要利用 Procmon 来捕获分析相关信息;
  • ...
当然此处并非仅仅赞扬 Xcode 吐槽 Visual Studio,只是客观表达一下我上手之后的感受。在进行桌面端尤其是 Windows 研发时,所使用的工具更多了但是所关注的细节也更多了。不同于移动端「踩油门就走的自动挡车」,桌面端(Win 端)研发更像搓「手动挡」,只知道「踩油门」是不够的,需要有更完善的全局视角,要学会多模块之间的「油离配合」才能把车开好。
当然,在 ChatGPT 推动「AI 时代」再次成为热点的当下,未来 1~2 年后这些琐碎的细节或许会彻底退出大部分技术同学的视线。

闯过的那些「关卡」

讲完了整体的感受,下面我们就从一个移动端 iOS 开发同学的视角具体来看看,在走向桌面 Windows 研发「打怪升级」的过程中,我们需要闯过哪些「关卡」。

第一关:开发语言 C++

对于经常使用或者熟悉 C++ 的同学来说,这当然就不是一个问题了。但是对于我这种学校出来就没用过几次 C++ 的来说,重新学习并在实际生产环境中把 C++ 用起来着实也花费了一番功夫。
从个人体感来看,学习 C++ 不像是去历尽千难万阻翻越一座高山,更像是在脚踏实地一砖一瓦砌长城。这个过程没有一个明确的节点,说你只要达到这个节点你就「会」了。学习应用 C++ 最重要是需要不断的练习,通过实践来提升自己的水平,以点点滴滴的积累来构建自己的技术壁垒。
当然,这段话听起来像是「正确的废话」,把这段话的主语从 C++ 换成另外任何一门语言也是适用的。但是确实在我学习过的开发语言中,只有C++ 让我对上面这段话的感受最为强烈。
究其原因,我想主要原因可能是因为相比 Java、OC、JS、Dart、JS、Python 等其他高级语言,C++ 更加 古老、原始、繁杂。这门语言在实现上上暴露了太多的细节,使开发者为了更好的使用不得不去关注、学习并理解这些「Under the Hood」里的内容。当然这些特点对于喜爱的人来讲恰恰又是他们迷恋的地方,因为这些原始的实现方式让技术同学有更多的发挥空间,可以随心随遇组合自己所需要的功能,而不受各种条条框框的限制。
好比是哈尔的城堡,尽管看起来非常的原始、破旧,但是他能走、能跳,还能飞!C++ 以一种略显古老的状态发展到了非常强大的水平。

第二关:研发 IDE Visual Studio

前面已经有对 Visual Studio 使用感受的说明,在此就不再啰嗦了。总的来讲,无论你是从 Xcode、Android Studio 等其他平台型 IDE 迁移而来,还是从 VS Code 等通用 IDE 迁移过来,在上手 Visual Studio 的时候都需要先花点时间熟悉一下它「强大的功能」。
号称史上最强的 IDE 确实在功能丰富度上是无可比拟的,其配合 Windows 开发工具包能完成很多在其他平台上不可想象的功能。但是就像微软其他产品一样,尽管 Visual Studio 有着非常强大的功能,但是在交互设计上却是一言难尽。
正因为其交互设计上缺乏层次感,导致在上手使用时感觉其功能「又多又不够」:
  • 是因为其确实有很多很强大的功能;
  • 不够是因为有些功能使用繁琐,或者说很难达到预期的效果,比如非常高频的实时语法与错误检查。不知是否因为钉钉项目内有特殊性,但在实际开发中经常会遇到 IDE 编辑窗口中语法飘红的地方没问题,但是看起来的一切安好的代码则可能存在编译问题
看到这里可能有些同学会表示不认可了,“Visual Studio 中不是有 IntelliSense 吗?你所说的代码补全、错误检查等等功能在中 IntelliSense 都有”。确实,如果看微软关于 IntelliSense 的介绍,这确实是一款编码辅助利器,堪称早期的「Copilot」。但理想很丰满现实很骨感,不知是否因为配置不对,在实际使用中,IntelliSense 相比人工智能更像是一个人工智障,提示不准、误诊误报。它非但没有把复杂问题简单化,反而因为提示了太多不对的信息导致简单的问题被复杂化。建议大家谨慎使用。
但归根结底 Visual Studio 还是一个强大的 IDE,只要我们在使用时能够沉下心来慢慢学习,不盲目套用其他 IDE 的使用经验,那么在上手之后会发现 Visual Studio 确实也是一个非常趁手的工具。

第三关:UI 开发框架 QT/Duilib

不同于移动端以及 Mac 平台自带的标准 UI 开发框架,桌面端 Windows 平台并没有一个统一的开发模式,虽然官方推出过 WPF、UWP、Windows Forms、WinUI 等多种方案,但是在国内似乎热度并不高(不排除是因为我接触的少而理解有误),更主流的开发方式还是以 QT 和 Electron 为主。
Electron 是一种基于 JS 的 Web 渲染方案,钉钉内并未使用,但 QT 钉钉内有所涉及;除 QT 以外主要的采用的是 Duilib,这两种都是以 C++ 为开发语言,QT 底层依赖自绘引擎,Duilib 底层依赖 DirectUI。
因为我所涉及的项目主要是基于 Flutter 来做跨端,因此在 Windows 端原生 UI 开发方面的经验并不多。但在这有限的开发经验中,Windows 端原生 UI 开发给我的感觉与 C++ 非常类似,那就是很原始。因为涉及年代很早,导致在设计实现上缺少现代 UI 框架的便利性,比如常见的窗口管理、页面路由、事件分发响应等,在使用便利度上比其他平台要复杂很多,进而带来研发效率低、健壮性差、稳定性不足等问题。
不过反过来也因为 Windows 原生开发的这些问题,给 QT/Electron/Duilib 这些三方 UI 框架提供了成长空间。尤其是现在 Flutter 也慢慢成长起来,作为一个由 移动端 发展而来的跨端 UI 框架,Flutter 在设计成熟度、开发便利度上相比现有桌面端开发框架更具优势。随着 Flutter 在桌面端成熟度的提升,相信未来其在桌面端应用上也会有更大的空间。
如上所诉,因为项目设计到的 Windows 原生开发经验有限,因此在 UI 开发框架上我可分享的经验也不多,仅有一些皮毛,后续有机会深入了解之后再做补充。

第四关:编码问题

我作为一个「UTF-8 温室成长起来的乖宝宝」,在经历过 Windows 编码问题毒打之后才发现,原来哪怕仅仅是让控制台输出我能读懂的字符可能都是一个非常艰巨的任务。
不同于 Linux 等其他默认使用 UTF-8 编码的系统,Windows 上英文环境默认使用 ASCII 编码、中文默认使用 GBK 编码。因为大家默认编码方式不同,这样就带来一个非常严重的问题,如果技术同学在编码时未明确指定编码方式,在不同系统/工具之间做内容传输时,可能出现鸡同鸭讲的乱码问题。
有些同学可能会说,“这跟 Widnows 平台没什么关系,编码问题是一个通用问题,如果开发时不注意自己的编码格式,那么无论在什么平台开发都可能出现问题”。此话不假,编码确实是一个通用问题,不应该与 Windows 平台绑定起来。但 Windows 端默认编码方式不同所造成更大困扰也是一个实时:
  1. 在执行数据传输时,需要跨不同运行环境、运行域的场景,大部分同学会注意明确指定编码;但是对一些本地场景,比如脚本工具场景的日志输出场景,大部分人没有指定编码的习惯;
  2. UTF-8 是一个约定俗成的准标准,已经在绝大部分情况下成为了默认共识;并且于开发工具在当前系统开发验证时一定是可以正常运行的;
  3. 在这一默认共识的情况下,对非约定的场景导致的问题,需要开发者有意的主动兼容,但是这一主动兼容行为并非强制的;那么如果一个工具在开发的环境和运行的环境默认编码不同,则很容易导致出现异常;
也就是说,很多时候大家在 Linux 或者 macOS 平台上开发的工具以及脚本默认都是 UTF-8 标准并且功能正常。但如果某一天需要运行到 Windows 系统上,你必须主动的来兼容 Windows 上的编码标准,否则你的工具或者脚本就没办法准确输出预期的内容。
更进一步的问题是,如果这个工具或者脚本是由自己或者一方团队开发,那么兼容成本或许还好。但是如果这个工具或者脚本是二三方提供、并且他们没有 Windows 平台使用的诉求,那么想尝试解决这种问题将是一个极大的挑战。
在进一步的基础上更进一步的问题是,如果你只使用一个工具或者脚本还好,如果你使用的工具或者脚本中还存在嵌套其他工具或者脚本的情况,那么问题就......
在 Windows 系统上编译 Flutter 产物以及 FlutterEngine 时,我就花费了很长时间来解决工具输出信息乱码的问题。在这一过程中有一个非常强烈的感受:在复杂系统间协同时,「统一标准」才是最重要的设计。在生产环境中,一个统一的方案实际意义要大于多个局部最优的孤立方案。
非常可惜,即使到现在我也没有完全搞定 Windows 系统上的编码问题,比如现在使用一些工具编译项目时,还是可能会出现莫名的乱码信息,到现在只能见一个处理一个,非必要的问题就忽略。
在处理项目中遇到问题的工程中,也沉淀了一些小经验,简单来讲主要有:
  1. Visual Studio 建议使用英文,不要使用默认的中文环境;
  2. 遇到乱码时注意检查命令行换机的 VSLANG 环境变量;
  3. 实在不好解决的问题,可尝试使用 iconv 将输出内容转码;

第五关:问题分析工具 PDB 与 WinDbg

在日常开发中,仅仅会写代码肯定是远远不够的,我们还需要具备在出现问题的时候分析问题、解决问题的能力。
Windows 端发生问题时,比如典型的是 Crash,我们可以通过技术手段收集用户在异常发生时内存的 dump 信息。但是与其他平台一样,dump 信息里是一堆很难理解的地址与偏移,仅仅靠这一份文件我们无法分析导致问题的具体原因。这个时候我们就需要借助 PDB 与 WinDbg 来帮我们找到更多信息:
  • PDB(Program Database)是 Windows 端上程序使用的调试与符号化文件,其中最重要的是调试信息以及符号表;
  • WinDbg 是 Windows 平台上非常强大的调试工具,可用于崩溃分析、内存分析、异常调试等功能;
通过 dump + PDB + WinDbg 我们基本可以还原异常发生时用户内存状态。当然工具只是基础,希望准确高效的分析问题,更重要的还是操作工具技术同学的经验以及能力。因此在了解这些工具基本用法之后,对我们来讲更重要的还是要多多实践与学习。

第六关:工程化问题 包管理 模块化与构建

Windows 端工程化相关问题其实更多应该是 C/C++ 工程化问题,因为我关于这块并不专业,在此不再妄议。但是这些知识是大型复杂项目所必须解决的,我在学习桌面端/Windows 研发时也花费了一些精力在这上面,故这也把工程化算上是我们需要闯过的一大关卡。
在钉钉桌面端团队中有一只专门的小组同学同学在攻坚类似问题,感兴趣的同学可以线下咨询了解。

收获与感悟

前面这些内容差不多就是我在学习上手桌面端 Windows 研发中一些浅薄的经验,虽然略显分散不成体系,但希望也能给感兴趣的同学提供一些参考。既然文章是「开发之旅」,经验部分分享完,下面想顺着这个话题来简单聊这过程中的一些个人收获与感悟。
如前文所述,我学习桌面端研发的主要背景还是因为要在钉钉内探索推进基于 Flutter 的 UI 层跨端方案。推进 Flutter 在业务上落地与学习积累桌面端 Windows 研发经验这两件事情在很长一段时间内都属于交替并行的一个状态,两件事情相互交织融合。并且在这过程中通过与不同技术背景、不同技术栈同学交流中,对技术同学常挂在嘴边的「跨端」「平台」「技术人」这些关键词也有了一些新的理解。

关于跨端

首先讲讲我对跨端研发的理解。我认为在面对跨端研发这个命题的时候,我们需要明确开发语言只是基础,是「车票」与「敲门砖」。学习跨端研发绝不等于学习一门支持跨端的语言,也就是说并不是我学会了 C/C++ 这种具备多端编译的语言,就具备做跨端项目开发的能力。掌握语言只是跨端研发的的起点,不是终点
在工程应用中,学习某种跨端开发语言并非是目标,语言是工具,是服务于目标达成的手段。作为一个工具,衡量其是否优秀,语言与语法只是表象,除此以外还有语言背后所对应的基础库完善程度、开发框架丰富程度、标准成熟度、生态活跃度等等。
掌握一门优秀的跨端语言之后,剩下我们还需要尽量多去掌握目标平台的特性。比如我们想开发一个可以横跨移动端与桌面端的模块,那么在交互设计上我们必须明白移动端交互主要是手势,但是桌面端主要是是鼠标键盘。比如对于一个列表,在移动端下拉刷新、长按操作、滑动删除等都是基本操作;但是在桌面端更多是键盘快捷键刷新、鼠标右键来操作。
当然这只是一个比较简单直观的例子,总的来说如果技术同学有意向去做跨端开发,切不可把自己的目标定在学习一门跨端语言上,语言只是基础、是工具,如何使用工具进行创造才应该是我们所追求的目标。

关于平台

其次来聊一下平台。在跨端项目推进过程中,我发现有一个值得关注的现象,在长时间从事某一领域研发之后,技术同学或多或少会把 OS 平台、语言或者工具的优势当做个人的技术壁垒
不同于「什么是世界上最好的开发语言」这种几十年也没有明确结果的争论,关于 OS 平台的优劣大家似乎有着一套相对统一的共识:
  • Apple 平台(iOS&macOS)的原生实现性能和体验最好;
  • Android 平台的用户量大,终端设备多,性能和体验一般;
  • Windows 平台开发比较「小众」,体验大致等于桌面端的 Android;
  • Web 平台生态最繁荣、应用范围最广、标准化程度最高,有天然跨端的优势,但性能与体验一般;
每一种平台都有自己的特点,有自己的优势、也有自己的劣势,站在现在的时间点来看每个平台 OS 都有自己存在的价值,都有自己的应用场景,一片静好。
但是,这些都是平台属性而非技术同学的属性,是现状而不一定是未来。作为技术同学尤其是要警惕把平台优势作为个人技术壁垒的情况,以发展的眼光来看待事物,抱着开放谦卑不断学习的态度才能让自己保持竞争力,切不可在平台优势不再的时候,才意识到自己是「涨潮中裸泳的那一个」

关于技术人

最后来聊一下我们技术人。研发同学虽然喜欢自我调侃叫自己为「码农」,但更多时候大家还是喜欢「技术人」这个称号。在「技术人」这个大圈之下,还有一撮有更高层次追求的同学更喜欢以「Geek」自居,也就是极客
在技术人的圈子里,如果你能得到他人给予 Geek 或者 具备 Geek 精神 的评价,那是是非常有价值的事情,因为这意味着你得到了圈子内人的认可,甚至说某些方面已成为圈内翘楚。潜移默化中对 Geek 精神 的追求已成为有自我要求的技术人不断努力的一个方向。
但是,在这过程中我们也应时刻提醒自己:Geek 精神应该是一种品质,而不是一个标签
  • 所谓 品质,是由内而外的,是对思考方式、做事方式的总结,是一种沉淀之后的结果;
  • 所谓 标签,是浮于表面的,是为了特定结果而执行的动作,是一种精心布置的装饰;
我们所追求 Geek 精神的重点应在于观察、专研、突破、实践,重在做而不是说。与所有技术人共勉,愿我们最终都能努力达到自己期望的高度。

总结

以上即是我作为一个 iOS 工程师在学习桌面端、尤其是 Windows 端研发的些许经验以及感悟分享。在这过程中我得到了多位同学的悉心指导,从这些资深 Windows 研发同学身上学习了很多知识,让我受益匪浅。
文章总的来说内容相对琐碎,但在集团 ATA 交流中,桌面端研发尤其是 Windows 研发一直以来都是一个略显小众的场景,本文虽简陋但也希望能引起大家一些关注和思考,为桌面端技术交流添砖加瓦。另外钉钉客户端有着一支在专业度和能力上都数一数二的桌面端团队,对桌面端研发感兴趣有想法的同学可以找我们桌面负责人交流探讨。
继续阅读
阅读原文