开源Windows应用SumatraPDF作者分享的15年的经验教训

开源Windows应用SumatraPDF作者分享的15年的经验教训我发布了第一个版本的SumatraPDF是在2006年。那是15年前的事了,这似乎是进行回顾的好时机。

大家好,欢迎来到IT知识分享网。

我发布了第一个版本的SumatraPDF是在2006年。那是15年前的事了,这似乎是进行回顾的好时机。

该应用程序

SumatraPDF是一个适用于Windows的多格式(PDF、ePub、Mobi、漫画书、DjVu、XPS、CHM)阅读器,目前看起来像这样:

开源Windows应用SumatraPDF作者分享的15年的经验教训

准则

SumatraPDF是一个用于Windows的开源文档阅读器。它一开始是一个PDF阅读器,所以叫这个名字。随着时间的推移,我增加了电子书格式(epub、mobi)、漫画书(cbz、cbr)、DjVu、XPS、图像格式等。

这大约是12.7万行的C++(不包括其他人写的库)。

它是根据Win32 API编写的,没有使用Qt这样的GUI抽象库。这有助于使它尽可能地小而快。

几乎所有的内容都是由2个人写的,偶尔也有其他人的贡献。

编写的代码量实际上更高。这是长期运行的代码库的性质,代码被写入和重写。我们删除、增加、改变。

这是一个副业,在工作之余完成,不是全职工作。在一个应用程序上工作的日常磨练是怎样的?

它看起来像这样:

开源Windows应用SumatraPDF作者分享的15年的经验教训

你也可以偷看一下我的开发日志. 我一年前才开始写,所以只涵盖了15年中的1年。

我为什么创建SumatraPDF

SumatraPDF是我称之为意外的成功。

我从未想过要为Windows写一个PDF阅读器。

2006年,我在Palm工作,我的工作职责之一是为Foleo编写一个PDF阅读器。 Foleo,这是一款由ARM和Linux驱动的迷你笔记本电脑。你大概从未听说过Foleo,因为它在发布前几周就被取消了,原因我不清楚。

当时我不知道PDF很流行,但Palm管理层知道,这就是为什么他们决定PDF阅读器是一个必须的应用程序。我最终成为这个项目的(唯一的)开发者。

编写一个PDF渲染库是一项多年的工作。我们没有几年的时间,所以我用开源的渲染库

我的工作是写一个基本的PDF浏览器,使用Poppler将PDF页面渲染成内存中的位图,并将这些位图显示在屏幕上。

PDF是一种复杂的格式,一些PDF的渲染速度很慢。我想提高速度,因为杰夫-贝佐斯告诉我,速度是客户永远关心的东西。

意外的应用程序

提高速度的方法是对代码进行剖析,看看结果。

不幸的是,用于未发布的ARM硬件的工具链并不是很好。忘掉剖析器吧,孩子,庆幸你有一个C++编译器,不用像Steve Wozniak那样通过输入十六进制来输入汇编。

Windows有体面的剖析器,所以我为Windows编译了Poppler。

一旦我有了在Windows上工作的库,我就写了一个最简单的GUI应用程序,可以显示页面并允许在页面之间进行导航。

你知道什么?我有一个用于Windows的简单的PDF阅读器。

我在我的网站上发布了它。它不能做什么,所以我把它标记为0.1版本。

如果你不为你的应用程序感到尴尬,那么你已经等了很久才发布它。

这个智慧的小插曲不是我想出来的,但我同意它。

获得早期用户,了解他们最想要的功能,这比辛苦几个月或几年,在你知道有人关心之前实现很多功能要好得多。

剖析、性能优化和对开放源码的贡献

回到剖析:我的计划成功了。

我对渲染时间最长的文件进行了分析,并进行了一些简单得出奇的、有效得出奇的优化。

如果说内存服务器,2个优化的效果是最大的:

  • 优化字符串类,使用所谓的 “小字符串优化”,即在字符串类中添加一个小的缓冲区,以容纳内联的小字符串(而不是总是为字符串分配内存)。字符串被频繁使用,而且大多数是小字符串
  • 通过将其转换为批量读取的方式来修复逐个字节的i/o。在一些代码路径中,代码的结构方式是为每个字节做一个虚拟的C++调用和一个C read()函数调用。这些都是非常便宜的,但当你做500万次的时候就不一样了。

作为一个好孩子,我确实向Poppler提交了我的修改。

就像我为开源项目做贡献的经验一样,这更像是一次失误而不是一次成功。

是的,我得到了13个提交,但这个项目并不活跃,维护者也不急于接受除小改动以外的东西。忘记任何重大的重构。

我不是一个自愿用头撞墙的人,所以我不再尝试。

(如你所见,我是一个出色的团队成员)。

代码质量

我想要它,你也应该想要它。

如何在大多数情况下独自工作,没有人做代码审查,没有专门的QA团队的情况下保持高的代码质量?

下面是方法:

  • 自己测试代码。在调试器中浏览新添加的代码,验证新添加的功能是否符合预期,并在一般情况下经常使用该应用程序。
  • 自动崩溃报告。不幸的是,它的构建很麻烦,但这是你为提高软件质量所能做的最重要的事情。简而言之:设置异常处理程序来捕捉应用程序中的崩溃,在崩溃处理程序中从服务器上下载符号以获得可读的callstack,创建一个崩溃报告,包括所有线程、程序和操作系统信息的callstack,记录并提交给服务器。在服务器上,处理这些文件并生成网页以方便查看崩溃情况。就像我说的:建立它是一件很痛苦的事情。一旦你有了崩溃,就偶尔看看它们,并试图找出出错的地方并加以修复。
  • assert()。断言是C++代码中公认的做法:只在调试构建中执行的额外代码,验证某些条件为真。如果不是,就说明出了问题,你应该进行调查。我写了我自己的类似断言的函数,在非调试的预发布版本中启用,这样我就能自动收到人们碰到这些条件时的错误报告。相信我:你自己做再多的测试,也无法与一千多人在使用该应用程序时做的所有不同事情相提并论。
  • 记录。在调查问题时,了解导致崩溃的事件序列是有帮助的。我的小型日志模块将日志记录到一个内存块中。这将与崩溃报告一起被发送。我也有一个选项,可以将日志记录到一个文件中,最近我还通过命名的管道将日志记录到一个单独的日志应用中。这很完美,因为大多数时候我并不关心日志,但当我关心时,我不想重启应用程序来启用日志。有了独立的日志应用,SumatraPDF一直在记录日志,当它检测到日志应用正在运行时,它也会向它记录。实施起来很简单:日志应用程序创建一个命名的管道,记录器打开这个管道(就像一个文件),如果打开成功,就意味着日志应用程序正在运行,它将读取我们写到管道中的日志。
  • 静态代码分析:C++编译器的最大警告级别,将警告变成错误,Visual Studio的`/analyze’选项,cppcheck,clang-tidy,GitHub的CodeQL。偶尔运行这些,并修复错误和警告
  • ASAN (地址消毒剂),是非常棒的。在Visual Studio 2019的某个点版本中被添加。以很小的性能代价,它可以检测你是否过度写入内存或试图读取未初始化的内存。我有一个启用了ASAN的配置。它的速度足够快,可以作为常规构建使用。
  • 压力测试。Sumatra的工作主要是渲染复杂的文件格式。由于格式的复杂性,在特定的文件中经常出现崩溃的情况。为了确保没有崩溃,我写了一个压力测试代码,读取并渲染一个目录中的所有文件。在发布之前,我通常会在我多年来积累的大量测试文件上运行它。
  • 单元测试。我没有太多的单元测试,它们主要用于测试低级功能的边缘情况,如字符串格式化。他们偶尔会发现错误。
  • 内存泄漏。令人惊讶的是,很难找到一个容易使用的内存泄漏检测工具。我正在开发一个非常简单的内置泄漏检测器。在此期间,我正在使用 Dr. Memory.它可以工作,但速度超慢。

频繁发布

当你没有很多功能的时候,改进应用程序是快速而简单的。实现 “转到 “对话框(在0.2版中实现)并不费力。

一方面,我不想太频繁地发布,但我也确实希望用户能尽快获得新的功能。

我对新版本的政策是:当至少有一个明显的、用户可见的改进时才发布。

网络应用程序将其发挥到了极致(一些公司每天都会向生产部署多次)。

在桌面软件中,这就有点复杂了,我必须建立一些功能来使它变得简单,即添加一个新版本的检查,编写一个可以更新程序的安装程序。

BTW:我的意思是 “与编写的新代码的数量成比例的频繁”。SumatraPDF的发布在绝对意义上并不频繁,但如果你考虑到它是一个兼职的、下班后的项目,就会很频繁。

像对待商业软件一样对待开源项目

大多数开源项目可能不属于这个类别,但如果你想让你的开源项目尽可能地成功,就像它是一个软件公司的商业产品那样行事。

它在实践中意味着什么?

从第一天起,我就为这个应用程序创建了一个网站。它有屏幕截图,有文档,很容易下载和安装。当然,Reddit上的一个好心人称它是 “一个6岁孩子做的网站”。这里的教训是双重的:

  • 无视仇恨者和混蛋
  • 一个由6岁儿童建立的网站总比没有网站好。它不一定要漂亮,但一定要有功能。

我做了基本的SEO。没有超出谷歌 “SEO 101 “文件的内容:只要注意URLs,放置正确的元数据,使用正确的关键词。

我有一个论坛,供用户提出问题、提交功能请求,偶尔也会相互支持。

我使安装过程尽可能地简单。

凡是促进商业软件的好主意,对开源项目来说也是一个好主意。

在汽车运行中切换发动机

在某种程度上,我决定从Poppler切换到 mupdf因为mupdf更好,并且得到了积极的维护。

改变应用程序以使用完全不同的库,这不是一个下午就能做到的。

在甚至不能编译的代码上工作很长时间,这让人士气低落。

为了保持东西的编译,同时也为了支持替代的渲染引擎,我为渲染引擎开发了一个抽象。

该引擎将提供用户界面所需的功能:获取文档中的页数、每页的尺寸(以计算布局)、将一个页面渲染成位图等。

我比大多数程序员(至少是那些喜欢在Hacker News上发表意见的人)更不热衷于抽象,但在这种情况下,它对我很有帮助。

我能够逐步将程序形式从使用Poppler API转换到通过引擎抽象使用Poppler,再到通过引擎抽象使用mupdf。

有一段时间,我同时支持这两个引擎,但最终我改成只支持mupdf,以保持应用程序的小型化。

这为通过相同的抽象来支持其他格式打开了大门。

简单性与可定制性

简洁性是卖点。

我从Mozilla Firefox的历史中学到了这一点。

在Firefox之前,有一个Netscape Navigator。它是一个集网络浏览器和电子邮件客户端于一身的应用程序的怪兽。

网景公司无法控制自己,不断增加功能,导致了非常复杂的用户界面。

Mozilla内部的一小群叛徒分叉了代码,专注于简单的用户界面。

简单的Firefox比复杂的Navigator更受欢迎,最终完全吃掉了它。

从一开始,我的目标就是要让SumatraPDF的用户界面尽可能的简单。一个80 ⁄20 应用程序:80%的功能和20%的用户界面。

这需要解决。我不断收到在工具栏上添加更多图标的请求,而我不得不不断地说 “不”,因为为满足10%的用户而在工具栏上添加2个图标,会使100%的用户的应用程序变得更糟。

另一个陷阱是额外设置的警笛声。有时人们建议,程序不应该做X,而应该做Y。他们不愿意删除X,而是建议添加一个新的用户界面设置”[ ]做Y而不是X”。

设置对话框有100个设置,这不是一个好的解决方案。它使每个人的应用程序变得更糟,因为他们的选择太多,而且把重要的选项隐藏在非重要的选项的海洋中。

更不用说每一个条件行为都需要更多的代码,更多潜在的错误和更多的测试。

话虽如此,我也相信可定制性是很重要的。我相信,Winamp之所以成为一个占主导地位的音乐播放器(在当时),很大原因是它有能力给整个用户界面换肤。

一些高级功能可能只有20%的用户使用,但这些用户很可能是权力用户,他们会比其他80%的用户更多地宣传该应用。

我对用户界面的简单性与可定制性的解决方案:高级设置文件。

我设计了一个简单的、人类可读(和人类可写)的文本格式,用于 高级设置.想象一下JSON,但要更好。

我懒得写用户界面来改变这些高级设置。我只是用该文件启动notepad.exe。当用户改变设置并保存文件时,我重新加载并应用这些改变。

水,我的朋友

变化是唯一不变的。我们必须适应世界的变化。

我无法相信有多少流行的项目仍然使用蹩脚的Sourceforge作为源码库或邮件列表。

实际上,我可以相信:改变事物需要努力,阻力最小的途径是什么都不做。

我从Sourceforge开始,转到code.google.com,然后到github.com。

我换了三次论坛软件。

我已经添加了一个浏览器插件,然后在浏览器停止支持此类插件时将其删除。

我把存储偏好的格式从二进制改为人类可读的文本。

Windows XP从大多数用户使用的操作系统变成了不再被支持(在微软停止支持它很久之后)。

起初我只有32位的构建,现在我两者都有,但强调的是64位的构建。

跳出框框思考

跳出盒子思考是很难的,因为盒子是看不见的。

SumatraPDF并不是有史以来第一个PDF阅读器应用程序。

但大多数PDF阅读器并没有成为多格式的阅读器。

事后看来,支持尽可能多的文档格式是一个明显的想法,但我花了5年时间才意识到这一点。

大多数阅读器仍然是单一格式,我相信多格式有助于SumatraPDF的普及。

我不能说这是完全独特的想法。在SumatraPDF之前,早就有多格式图像查看器,我可能是受到了它们的启发。

小而快–两者兼得

按照现在的标准,SumatraPDF很小(安装程序小于10MB),而且可以立即启动。

我相信个头小、看起来快是采用的一个重要原因。

这又回到了杰夫-贝佐斯的智慧:永远不会有用户想要臃肿和缓慢的应用程序的时候,所以小而快是一个永久的优势。

我如何保持SumatraPDF的小型化?

我避免不必要的抽象。Window的控件系统在编程时是个巨大的麻烦。我可以使用Qt、WxWindows或Gtk等包装器。它们更容易使用,但会导致即时的、巨大的臃肿。

我并不害怕自己写东西的实现。我有自己的JSON、HTML/XML解析器,其大小仅相当于这些任务的流行库的一小部分。

我积极地利用Windows中包含的丰富功能。

比方说,我需要做一个网络请求。我可以包括一个像curl这样的怪物库,或者我可以使用win32 APIs写300行代码。我写了300行代码。

没有臃肿是很难注意到的,因为它不在那里。

我最讨厌的是过度使用XML来存储数据。

当我在Palm工作时,我参加了一个关于手机自动更新系统的设计会议。它的一部分是在图像中存储关于当前版本的信息,下载关于最新版本的信息并进行比较。

开发者决定使用XML来存储这些信息。对于存储版本号这样的简单信息来说,这似乎是一个很大的臃肿。仅仅是一个符合要求的XML解析器就是大量的代码。当然,一个简单的二进制格式会更容易实现,我建议说,但被忽略了。

如果你没有权力解雇某人,你的想法就会被忽视。

(如你所见,我是一个伟大的团队成员)。

为了存储高级设置,我设计并实现了一种文件格式,它比XML小,人类可读可写,只需几百行代码就可以实现。它和JSON一样强大,甚至更具可读性。

它是如此简单,以至于在实现它之后,我有时间为C++对象实现一个序列化系统和一个Go代码生成器。要增加更多的设置,我不需要写更多的C++代码。我只需将数据定义添加到Go生成器中,重新运行它,就能自动生成数据驱动的C++解析。

这是我的项目,我就像这样做。

当有人付钱让你写代码时,你必须按照他们喜欢的方式来写。

从事没有报酬的代码工作的一大吸引力是,没有人可以告诉你该做什么或怎么做。

我的代码不会通过谷歌的代码审查,不是因为它不好,而是因为它经常是非正统的。在公认的教条之外。

(如你所见,我是一个伟大的团队成员)。

我总是把SumatraPDF作为我测试疯狂想法的游乐场。

通过不使用STL来最小化代码大小?这很疯狂,但我做到了。当然,在2006年,STL还不是很好。

我了解到Plan 9的C代码有非传统的#include文件方案,他们不在每个.h文件中放#ifdef包装器,以允许多次包含,而且.h文件不包含其他.h文件。因此,.c文件必须以正确的顺序包含他们需要的每一个.h文件。这有点麻烦,据我所知,没有其他现代C++代码库保持这样的纪律。

但这是我的项目,所以我做了,而且一直在做。它可以防止.h文件之间的循环依赖,也不会因为不小心重复包含相同的文件而导致C++构建时间的增加。

我实现了一个受CSS启发的UI系统。不是很好,但却是我的。而且我打算用一个不同的来代替。

因为我可以。

因为没有人可以告诉我不要这样做。

跨平台被高估了

SumatraPDF是一个毫不掩饰的 毫不掩饰地说,它是一个仅适用于Windows的应用程序。

支持其他平台(Linux、Mac、Android)是最频繁的要求之一。一个我不得不拒绝的请求。

首先,有一个务实的原因:我只是没有足够的带宽为3个平台写代码。

第二,我相信一个平台的优秀应用可以比3个平台的平庸应用更受欢迎。

回到第一个原因:我没有足够的带宽来编写三个优秀的应用程序。SumatraPDF之所以小,部分原因是我在用户界面上使用了win32 APIs。

一个人要想尝试跨平台的应用,唯一的办法就是使用Qt、WxWidgets或Gtk等UI抽象层。

问题是,Gtk很丑,Qt极其臃肿,WxWidgets几乎不能用。

测试是没有必要的,代码审查也是如此

我不是说测试不好,也不是说你不应该写测试或做代码审查。

我是说,它们是没有必要的。

教条是强大的。在我的公司生活中,有时我觉得写测试只是在进行运动。也许我们应该花更多的时间来写代码,我想?

但是,如果你试图向你的同事们提出关于更多测试和更多代码的细微观点,你会被烧死在木桩上,你燃烧的尸体会被扔给野狗。村里的孩子会用你的头颅来踢足球。

(如你所见,我是一个伟大的团队成员)。

然而,我确实知道,你可以在没有测试的情况下写出复杂的、相对没有错误的代码,因为我做到了。

我确实知道,你可以写出复杂的、相对没有错误的代码,而不需要任何人查看你的代码,因为我做到了。

如果没有人使用你的应用程序,那么谁会在乎它是否崩溃。

如果很多人使用你的应用程序,它崩溃了,他们会告诉你,然后你会修复它。

一夜成功需要十年时间

SumatraPDF是比较受欢迎的。不是Facebook的流行,也不是DOOM的流行,但比大多数应用程序更流行。这是一个可敬的流行水平。

这一切都从0.1版和涓涓细流的下载开始。在很多很多个月里,它一直是涓涓细流。

我不确定这里是否有一个教训。

成功往往需要很长的时间。

不幸的是,在这个阶段,它与(最终的)失败是无法区分的,所以如果你正在进行一个尚未成功的项目,并在争论是否应该继续或放弃时,这种智慧并不能帮助你。

开源不是一个好的商业模式。

如果你想赚钱,可以做其他事情:尝试销售软件、做咨询、建立SAAS并按月收费、抢劫银行。

我确实做过赚钱的实验,也赚了一些。

曾经有一段时间,AdSense会支付体面的CPM,所以我把AdSense广告放在网站上,赚了一些钱。我不再这样做了,因为费率确实急剧下降,而且不值得去烦扰别人。我的灵魂是有价格的,AdSense再也负担不起了。

现在我正在尝试使用Patreon和Paypal的捐款。每个月能赚到100多美元,但不会超过这个数字。

就像我说的:不要带着赚钱的目的去启动开源项目。

你很少能同时拥有:做你想做的事的自由和高薪,所以要选择对你更重要的东西。开放源代码给你自由,但不给你钱。

走向未来

我需要继续像水一样。

多年来,我一直抵制增加编辑功能。”我说:”它只是一个阅读器。但为什么不增加编辑功能呢?如果人们需要它,就给他们吧。

所有软件的未来都是作为一个网络应用。为什么不把SumatraPDF的精神带到网络上呢?

这些只是我今天的一些想法。

像水一样意味着5年后我将会有其他的想法,并以当时的情况为依据。

本文由闻数起舞翻译自 https://blog.kowalczyk.info/article/2f72237a4230410a888acbfce3dc0864/lessons-learned-from-15-years-of-sumatrapdf-an-open-source-windows-app.html

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/49070.html

(0)

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

关注微信