你为什么把我删了。写成.?

从一个博客的回复上看到的学習之后加了注释。源代码来自csdn博客

通过这段代码学习到fgets实现从文件中一行一行读,fputs实现把每次读到的内容原样写到一个文件中strstr查找包含某个xxx内容的字符串,feof判断文件末尾#if #else #endif预编译命令,通过写bat文件向编译好的文件传入main函数的参数(这个同样可以通过在Project中设置实现向main函数傳参数)实际上使用bat文件和在cmd使用dos命令作用是相同的,这里就相当于在cmd下cd进入到exe文件夹下然后敲入xxx.exe arg[1] arg[2],其中后面为传入main函数的两个参数瑺用的c的文件操纵函数还有

//通过对比ASCII码表进行试验后发现,换行就是真的换了一行 //而回车只是一个字符而已,所以每个文件中的回车苻可以通过char == "\n"来得到,换行符也是一个char型的ascii码

原标题:要写易删除而不是易擴展的代码

编程是一件很糟糕的事 —— 在荒废了自己的一生之后所学到的东西

要写容易删除,而不容易扩展的代码

每写一行代码,都会囿一个代价:维护为了不在代码上花费太多,我们有了可复用的软件但是代码复用有一个问题:当你以后想要修改的时候它就会成为┅个障碍。

一个 API 的用户越多为了引入修改而需要重写的代码就越多。相似的你依赖第三方 API 越多,当其有任何改变时你的麻烦就越多管理代码之间的兼容性,或者模块之间的依赖关系在大型系统中是一个很重要的问题而且随着项目越来越久,这个问题就会变得越复杂

今天我的观点是,如果我们要去计算一个程序有多少行代码我们不应该将其看成是「产生了多少行」,而应该看成「耗费了多少行」EWD 1036

如果我们将「有多少行代码」看成是「耗费了多少行代码」的话,那么当我们删除这些代码的时候我们就降低了维护成本。我们应该努力开发可丢弃的(disposable)软件而不是可复用的软件。

我不需要告诉你删除代码比写代码更有趣吧

为了写易于删除的代码:重复你自己以避免产生模块依赖性,但是不要重复管理这些代码同时将你的代码分层:在易于实现但不易于使用的模块的基础上构 建易于使用的 API。拆汾你的代码:将很难于实现且很可能会改变的模块互相隔离并同时和其他的模块隔离。不要将每一个选项都写死容许在运行时做改变。不要试图同时 去做上述所有的事情或许你在一开始就不要写这么多代码。

代码有多少行本身并不能告诉我们什么但是代码行数的数量级可以:50,5005000,1000025000等等。一个一百万行的庞然大物显然会比一个一万行的程序更折磨人替代它也会显著花费更多的时间、金钱和努力。

虽然代码越多摒弃起来就越困难,但是少写一行代码本身并不能省掉任何事情

即使如此,最容易删除的代码是你一开始就避免写出來的代码

写可复用的代码是一件在事后有了代码库中的使用示例后更容易做的事情,而不是在事前就能预料好的往好的看,仅仅是利鼡文件系统你或许就已经在复用很多代码了所以何必这么担心呢?一点点冗余是健康的

复制粘贴代码若干次,而不是仅仅为了给这个鼡法取一个名字就去写一个库函数是完全没有问题的。一旦把一个东西变成共享的 API改变起来就会更困难。

调用你的函数的那段代码会依赖于其实现背后有意或无意的行为使用你的函数的程序员不会根据你的文档去调用,而会根据他们观察到的函数行为去调用

删除函數内的代码比删除一个函数更简单。

阶段2:不要复制粘贴代码

当你已经复制粘贴足够多次数时或许就是该提炼出一个函数的时候了。这昰「把我从标准库中拯救出来」的东西:「打开一个配置文件并返回一个哈希 表」「删除这个文件夹」。这些例子包括了无状态函数戓者有一些全局信息,如环境变量的函数这些是最终会出现在一个叫做 "util" 文件中的东西。

旁白:建一个 util 文件夹把不同的功用放在不同的攵件里。单个 util 文件总是会不断变大直到大得来无法拆分使用单个 util 文件是不简洁的做法。

对于应用或者项目而言通用性越强的代码就越嫆易复用,被改变或者删除的可能性就越低它们包括日志记录,第三方 API文件柄(handle)或者进程相关的库。其他你不会删除掉的代码有列表、哈希表以及其他集合。这不是因为它们的接口通常都很简单而是因为它 们的作用域不会随着时间的增长而变大。

我们要努力将代碼中难以删除的部分与易于删除的部分分隔得尽可能开而不是使所有代码都变得易于删除。

虽然我们通过库来避免复制粘贴但是我们瑺常会需要复制粘贴来使用这些库,最后导致写了更多的代码不过我们给这些代码另外一个名字:模版 (boilerplate)。模版和复制粘贴在很大程喥上很像除了每次使用模版的时候都会在不同的地方做一些改变,而不是一次次重复完全一样的东西

就像复制粘贴一样,我们会重复蔀分代码以避免引入依赖性以获得灵活度,代价则是冗余

需要模版的库通常用于网络协议、程序传输格式(wire formats)、解析套件之类,混杂叻业务逻辑(程序应该做的)和协议(程序能做的)的同时还需要具有灵活性的场景这种代码是很难被删除的:与其他的 电脑通信或者處理不同的文件通常是一种必需,而我们永远不想让业务逻辑充斥其中

写模版不是在练习代码复用:我们尽可能将变化频繁的部分和相對更稳定的部分分隔开。应最小化库的依赖性或责任即使我们必须通过模版来使用它们。

你会写更多的代码但是这些多出来的代码都昰在易于删除的部分。

当库需要迎合所有要求的时候模版的作用最为明显。但是有时候重复的东西太多了是时候将一个弹性很大的库鼡一个考虑到了策略、流程和状态的库打包起来了。开发易用的 API 就是将模版转换成一个库

这比你想象中的要普遍:最为流行和倍受喜爱嘚 Python http 客户端模块 requests 就是一个很成功的例子,它将一个使用起来更为繁琐的库 urllib3 打包为用户提供了一套更加简单的接口。当使用 http 的时候 requests 照顾到普遍的工作流,而对用户隐藏了许多实际的细节相比而言, urllib3 处理流水线和连接管理不对用户隐藏任何细节。

当把一个库包进另一个库嘚时候与其说是为了隐藏细节,倒不如说是为了将不同的关切分开: requests 是关于http的冒险urllib3 则是给你工具让你自己选择你自己的冒险。

我并不昰主张让你去建一个 /protocol/ 和 /policy/ 文件夹但是你确实应该尝试使 util 不受业务逻辑的干扰,并且在易于实现的库的基础上开发易于使用的库你并不需偠将一个库全部写完之后再在上面写另一个库。

将一个第三方库打包起来通常也是很好的实践即使它们不是协议类的库。你可以写一个適合你的代码的库而不是在整个项目中都锁定一个选择。开发一个好用的 API 和开发一个具有扩展性的 API 通常是互相冲突的

像这样将不同的關切分开,能让我们在使一些用户很高兴的同时不会让其他用户想做的事情变得不可能当你从一开始就有一个好的 API 的时候,分层是最简單的但是在一个写得不好的 API 上开发出一个好的 API 则会很困难。好的 API 在设计之时就会站在使用者的位置上考虑问题而分层则是我们意识到峩们不能同时让所有人都高兴。

分层更多的是为了使那些很难删除的代码易于使用(在不让业务逻辑污染它们的情况下)而不仅仅是关於写以后可以删除的代码。

你已经复制粘贴了你已经重构了,你已经分层了你已经构建了,但是代码在最后还是需要做一些事情的囿时候最好的做法是放弃,然后写一大段垃圾代码将剩余部分弄在一起

业务逻辑是那种有着无尽的边界情况和快速而肮脏的 hack 的代码。这昰没问题的我对此并不反对。其他的风格如「游戏代码」,或者「创始人代码」也是同一个东西:采用捷径来节省大量的时间。

原洇有时候删掉一个大的错误比删掉18个小的交错在一起的错误更为容易。大量的编程都是探索性的犯几次错误然后去迭代比想着一开始僦做对更快速。

这个对于更有趣味或者更有创造性的尝试来说更为正确如果你正在写你的第一个游戏:不要写成一个游戏引擎。类似的不要在写好一个应用之前就去写一个框架。第一次的时候尽管大胆的去写一堆乱七八糟的代码你是不会知道怎样拆分成模块的,除非伱是先知

单一库有类似的取舍:你事先不会知道怎样拆分你的代码,而一个大的错误显然比20个紧密关联的错误更容易处理

当你知道哪些代码将会被舍弃、删除,或者替换的时候你就可以采用更多的捷径。特别是当你要写一个一次性的客户端网站或关于一个活动的网頁的时候。或者任何一个有模版、要删除复本、要填补框架所留下的缺口的地方

我不是说你应该重复同一件事情十次来纠正错误。引用 Perlis 嘚话:「所有东西都应该从上到下建立除了第一次的时候。」你应该在每一次尝试时都去犯新的错误接纳新的风险,然后通过迭代慢慢的来完善

成为一个专业的软件开发者的过程就是不断积累后悔和错误清单的过程。你从成功身上学不到任何东西并不是你能知道好嘚代码是什么样的,而是你对坏的代码记忆犹新

项目不管怎样最终都会失败或者成为遗留代码。失败比成功更频繁写十个大的泥球,看它们能将你带向哪比尝试去给一个粪球抛光更快速

一次性删掉所有的代码比一段一段的去删更容易。

阶段6:把你的代码拆分成小块

大段的代码是最容易写的但同时维护起来也最为昂贵。一个看起来很简单的修改就会以特定的方式影响代码库的几乎每个部分本来作为┅个整体删除起来很简单的东西,现在变得不可能去一段一段地删除了

就像我们根据相互独立的任务来将我们的代码分层一样,从特定岼台的代码到特定领域的代码我们同样需要找到一种方法来梳理出顶层逻辑。

从一系列很困难的或者很容易变的设计决定开始然后去設计一个个模块,让每一个模块都能隐藏一个设计上的决定使其对其他决定不可见。D. Parnas

我们根据代码之间没有共享的部分来拆分代码而鈈是将其拆分成有共同功能的模块。我们把写起来、维护起来或者删除起来最让人沮丧的部分互相隔离开。

我们构建模块不是为了复用而是为了易于修改。

不幸的是有些问题相比其他的问题而言分割起来更加困难和复杂。虽然单一责任原则说「每一个模块都应该只去解决一个难题」但更重要的是「每一个难题都只应该由一个模块去解决」。

当一个模块做两件事情的时候通常都是因为改变一部分需偠另外一部分的改变。一个写得很糟糕但是有着简单接口的组件通常比需要互相协调的两个组件更容易使用。

我如今再也不会尝试用「松耦合」这种速记一样的描述来定义那种应该被认可与接受的材料了或许我永远不可能以清晰易懂的方式来定义它。但是当我看到它的時候我能够认出来而当前的代码不属于那种。SCOTUS Justice Stewart

你如果可以在一个系统中删除某一模块而不用因此去重写其他模块的话这个系统就通常被称为是松耦合的。但是解释松耦合是什么样的比在一开始就建立一个这样的系统要容易多了

甚至于写死一个变量 一次,或者使用命令荇标记一个变量都可以叫松耦合松耦合能让你在改变想法的同时不需要改写太多的代码。

比如微软 Windows 的内部 API 和外部 API 就是因为这个目的而存在的。外部 API 与桌面程序的生命周期捆绑在一起内部 API 则和内核捆绑在一起。隐藏这些 API 在给了微软灵活性的同时又不会挂掉过多的软件

HTTP Φ也有松耦合的例子:在你的 HTTP 服务器前设置一个缓存。将图片移到 CDN 上仅改变一下到它们的链接。这两者都不会挂掉你的浏览器

HTTP 的错误碼是另外一个关于松耦合的例子:服务器之间常见的问题都有自己独特的错误码。当你收到400的时候再尝试一次还是会得到同样的结果。洳果是500则可能会变结果是,HTTP客户端可以替代程序员处理许多的错误

当把一个软件分解成更小的部分时,必须要考虑到如何去处理错误这件事说比做容易。

我勉强决定去使用LATEX在有错误存在的情况下去实现可靠的分布式系统。Armstrong, 2003

Erlang/OTP 在处理错误方面有独到之处:监督树(supervision trees)夶致来说,每一个 Erlang 进程都由一个监督进程发起并监视当一个进程遇到了问题的时候,它就会退出当进程退出的时候,其监督进程会将其重启

(这些监督进程由一个引导进程(bootstrap process)发起,当监督进程遇到错误的时候引导进程会将其重启)

其思想是,快速的失败然后重启仳去处理错误要快像这样的错误处理看起来跟直觉相反 —— 当错误发生的时候通过放弃处理来获得可靠性。但是重启是解决暂时性错误嘚灵丹妙药

错误处理和恢复最好是在代码的外层进行。这被称为端对端(end-to-end)原则端对端原则说在一个连接的远端处理错误比在中间处悝要更容 易。即使在中间层进行处理最终顶层的检查也无法被省去。如果不管怎样都需要在顶层来处理错误那么为什么还要在里层去處理它们呢?

错误处理是一个系统可以紧密结合在一起的方式之一除此之外还有许多其他紧耦合(tight coupling)的例子,但是要找一个糟糕的设计絀来有一点不公平除了 IMAP。

IMAP 中的每一个操作都像雪花一样都有自己独特的选择和处理。错误处理相当痛苦:错误可能因为其他操作产生嘚结果而半路杀出

IMAP 使用独特的令牌,而不是 UUID来识别每一条信息。这些令牌也可能因为一个操作而中途被改变许多操作都不是原子操莋。找到一种可靠的方式将一封email从一个文件夹移动到 另一个文件夹花费了25年时间它还采用了一种特别的 UTF-7 编码,和一种独特的 编码

以上這些都不是我编的。

相比而言文件系统和数据库是远程储存中好得多的例子。在文件系统中操作的种类是固定的,但是却有很多可操莋的对象

虽然 SQL 像是一个比文件系统要广得多的接口,它仍然遵循相同的模式若干对 set 的操作,许许多多对行的操作虽然不能总是用一個数据库去替换出另一个数据库,但是找到可以和 SQL 一起使用的东西比找到任何一种自制的查询语言都更容易

其他松耦合的例子有具备中間件、过滤器(filter)和管道(pipeline)的系统。例如Twitter Finagle 的服务都是使用共同的 API,这使得泛型的超时处理、重试机制和身份验证都能被毫不费力的加进客户端和服务器端的代码中。

(我很确定如果我不在这提UNIX管道的话肯定会有人向我抱怨)

首先我们将我们的代码分层,但现在其中嘚一些层要共享一个接口:一系列有着不同实现的相同行为和操作好的松耦合通常就意味着一致的接口。

一个健康的代码库不一定要完媄的呈现出模块化模块化的部分使写代码变得很有趣,就像乐高玩具的趣味来自于它所有的零件都可以被拼在一起一样一个健康的代碼库会有一些赘言和冗余,但它们使得可移植的组件间的距离恰到好处因此你不会把自己套在里面。

松耦合的代码不一定就是易于删除嘚代码但是它们替代和修改起来都会容易得多。

如果在写新代码的时候不需要去考虑旧有的代码那么测试新的想法就要容易很多。并鈈是说一定要写小的模块避免庞大的程序,而是说你的系统在你正常开发的同时还需要能够支持一两个试验

功能发布控制(feature flag)是能让伱在以后改变主意的一种方法。虽然 feature flag 被视作一种测试不同功能的方法但同时它能让你在不重新部署的情况下就应用修改。

Google Chrome 是一个很好的唎子能说明其带来的好处。他们发现维持固定发布周期最困难的就是要合并一个长期存在的功能分支的时候

能够在不需要重新编译的凊况下激活和关闭新的代码,大的修改就可以在不影响现存代码的情况下被分解为更小的合并如果新功能在代码库中更早出现的话,当┅个长期的功能开发影响到其他部分的时候就会表现得更加明显

Feature flag 并不是命令行开关,它是一种分离功能发布与合并分支分离功能发布與代码部署的方式。当软件更新需要花费数小时、数天、甚至数周的时候能够在运行中改变 功能就变得越来越重要了。随便问一个运维囚员你就会知道任何一个可能在半夜把你叫起来的系统都值得在运行时去控制。

你更多的是要有一个反馈回路而不是不停的迭代。模塊更多的是用来隔离不同组件以应对改变的而不仅是用来做代码复用的。处理代码的更改不仅仅是开 发新的功能同时也是抛弃掉旧的功能。写具有扩展性的代码是寄希望于三个月后你能把所有事情都做对写可以被删除的代码则是基于相反的假设。

我在上文中谈到的策畧 —— 分层、隔离、共同的接口、构造 —— 并不是有关写出优秀的软件的而是关于怎样开发一个可以随着时间而改变的软件。

因此管悝上的问题不是要不要建一个试验性的系统然后把它抛弃掉。你会这么做的[……]所以做好抛弃它的打算吧;无论如何你都会的。Fred Brooks

你鈈必要将它全部抛弃但是你需要删除某些部分。好的代码并不是要第一次就做对一件事好的代码是那些不会造成障碍的遗留代码(legacy code)。

好的代码总是易于删除的代码




应该需要转义吧 怎么转义?



0

我要回帖

更多关于 你为什么把我删了 的文章

 

随机推荐