单身开发者的人生智慧






3.97/5 (28投票s)
一位独立开发者给另一位的实用技巧和见解。
![]() |
目录
|
引言
自 2002 年 2 月 .Net Framework 发布以来,软件行业涌现出大量独立开发者。微软(尽管存在各种问题)改变了软件开发的格局。大多数底层细节与业务逻辑无关,而这正是大多数开发者受雇要做的。 Jeffery Richter 对 .Net Framework 的看法是:“……在使用 .Net Framework 几年后……我再也不会回到以前的软件开发方式了……我真不敢相信我们程序员竟然忍受了这么久。”
好了,我的说教到此为止。Needless to say that if someone is wondering whether or not to learn a .Net language, I have a bit of advice: “Embrace the future”。
作为独立开发者,虽然节省了所有运营成本,但也存在一些缺点。主要原因在于你是一个公司的整个创意引擎。如果你心情不好,整个公司的心情都不好!而且,很容易变得自满,或者更糟:“懒散”,因为没有其他开发人员直接使用你的代码。
我说这些是因为作为一名独立开发者,我经历过这些挑战很多很多次。多年来,我积累了一些对我的职业生涯非常有价值的技巧。请记住,我并不是在写一本关于事情必须如何做的教学大纲。我只是想分享一些经验,如果你有不同的看法,我们可以求同存异。
假想的听众
编写代码时,很容易将所有东西都组织在脑子里。但请记住,一年后,你一定会忘记你今天是如何构建代码的。
技巧:编写面向外部受众的代码。如果你认为另一家软件公司无法理解它,那么很有可能一年后你也会无法理解它。让我们看看我为自己编写的一个“客户信息”数据结构。
public struct Cust { public string Address; public string Date; public string ContactAddress //... }
- Address 字段存储:街道、城市、州和邮政编码。(它很紧凑,对吧?)
- Date 字段存储客户的出生日期。(为什么要用 DateOfBirth 这样的名字,而我知道它的意思呢?)
- ContactAddress 字段存储客户的电子邮件地址。(任何人都可以区分 Address 和 ContactAddress,对吧?)
现在这一切对我来说“都有道理”,但我们都知道这样的设计在开发人员小组的第一次会议上就不会通过。为了面向外部受众,我需要做一些明显的更改。
public struct CustomerInfo { public string Address; public string City; public string State; public string Zip; public DateTime DateOfBirth; public string ContactEmail; //... }
一眼望去,任何开发人员都能掌握此结构的范围。这也能节省我将来的时间,如果我需要审查/编辑此结构。
代码的尘埃
有时我会遇到一个难以理解的代码段。当这种情况发生时,我学会了通过最初“粗糙”(冗长)地编码,然后使用调试器“逐行执行”代码来找出合适的算法,最后清理我的代码,可以节省很多时间。例如,如果你正在处理一行包含复杂计算的代码,请将计算分成几行代码,并使用单独的变量,这样你就可以看到每一行是如何与其他行协同工作的。术语“调试器”实际上是一个非常狭窄的术语,当你可以实时处理复杂代码时。(记住要清理代码中的“尘埃”)
集中和泛化
比尔·盖茨曾说过:“……代码行数是你的敌人。”这是什么意思?你编写的未经测试的代码越多,调试时间就越长(以及更多的阿司匹林瓶)。这仅仅是因为没有人第一次就能写出完美的应用程序。在过去的两年里,我开发了一个经验法则,称为:集中和泛化。
集中
如果一段代码被使用超过两次,请将其集中到一个方法或函数中。此规则也适用于简短但复杂的算法。通过集中,可以减少必须调试的代码量。
在下面的示例中,我一直在开发一个文本编辑器,允许用户通过多种方式指定字体:弹出对话框、工具栏按钮等。对于所有这些代码,我发现自己为每一种都使用了相同的代码块。通过以下代码,我将该块集中到一个函数中,该函数根据三个参数返回一个新字体。
public Font BuildFont(FontFamily fontFamily, float size, FontStyle style) { if (fontFamily == null) throw new ArgumentException("FontFamily cannot be null!"); if (size <= 0) throw new ArgumentException("size must be greater than zero!"); bool errorCreatingFont = false; FontStyle styles = FontStyle.Regular; FontFamily ff = fontFamily; if (((style & FontStyle.Bold) == FontStyle.Bold) && ff.IsStyleAvailable(FontStyle.Bold)) styles |= FontStyle.Bold; if (((style & FontStyle.Italic) == FontStyle.Italic) && ff.IsStyleAvailable(FontStyle.Italic)) styles |= FontStyle.Italic; if (((style & FontStyle.Underline) == FontStyle.Underline) && ff.IsStyleAvailable(FontStyle.Underline)) styles |= FontStyle.Underline; //if no style has been applied, we make sure that a default style is set the current font accepts. if (((style & FontStyle.Regular) == FontStyle.Regular) && !ff.IsStyleAvailable(FontStyle.Regular)) { if (ff.IsStyleAvailable(FontStyle.Bold)) styles |= FontStyle.Bold; else if (ff.IsStyleAvailable(FontStyle.Italic)) styles |= FontStyle.Italic; else if (ff.IsStyleAvailable(FontStyle.Underline)) styles |= FontStyle.Underline; else errorCreatingFont = true; } if (errorCreatingFont) throw new Exception("Error creating font type: " + ff.Name + "!"); return new Font(ff, size, styles); }
自从我测试了 BuildFont() 函数后,我就可以像信心十足地引用其他 .Net 方法一样引用它。
泛化
为了泛化,编写一个可以在各种场景下工作的函数。让我举个例子。假设你正在开发一个过滤客户姓氏的函数。
public string[] FilterCustomer(string[] lastNames) { if (lastNames == null || lastNames.Length == 0) return new string[0]; List<string> filtered = new List<string>(lastNames.Length); for (int i = 0; i < lastNames.Length; i++) { if (lastNames[i] != null && lastNames.Length > 0 && lastNames[i].ToUpperInvariant().StartsWith(someInternalVar)) { filtered.Add(lastNames[i]); } } return filtered.ToArray(); }我希望你能看到上面的函数非常受限。主要是因为它不允许调用者指定应该过滤哪些名称。我们还可以更改其他一些内容,使其成为一个更好的通用函数。例如,`lastNames` 参数限制调用者使用字符串数组。为了泛化这一点,我们可以使用 IEnumerable 作为接受的输入。此外,允许调用者指定过滤器是包含还是排除过滤条件会很好。让我们看一下泛化版本。
/// <summary> /// Filters a customer by their last name. /// </summary> /// <param name="lastNames">The unfiltered list.</param> /// <param name="startsWith">Filter criteria.</param> /// <param name="excludeMatch">If true, returns a list that does /// not contain the filter criteria. If false, returns a list /// that contains the filtered criteria.</param> public string[] FilterCustomer(IEnumerable<string> lastNames, string startsWith, bool excludeMatch) { if (lastNames == null) return new string[0]; //ensure that we are case insensitive... startsWith = startsWith.ToUpperInvariant(); bool blnStartsWith; Listfiltered = new List (); foreach (string str in lastNames) { blnStartsWith = (str != null && str.Length > 0 && str.ToUpperInvariant().StartsWith(startsWith)); if (blnStartsWith != excludeMatch) filtered.Add(str); } return filtered.ToArray(); }
`lastNames` 参数现在可以接受任何实现 IEnumerable 的输入。这意味着,如果调用者决定使用泛型 List
显然,还有许多其他方法可以进一步泛化,但希望你能明白这个意思。
顺便说一句,我给你一个关于 `startsWith.ToUpperInvariant()` 的技巧。我从微软内部人士那里得知,ToUpperInvariant() 比 ToLowerInvariant() 执行得更快。
稳定性胜于速度
我知道我们都必须注意编写臃肿/缓慢的代码,但我可以告诉你,客户会因为有 bug 的软件而抱怨你的时间比因为缓慢的软件更多!只有在你确定其必要性时才编写非托管代码!编写非托管代码就像潜入鲨鱼出没的水域,所以要确保你是一个好的游泳者!.Net Framework 当然并不完美,但每天都在取得进步,使其变得更好。
无论如何都要让它看起来不错!
我最讨厌的事情之一是格式不佳的代码。它可能完美执行,但如果你必须像编译器一样去阅读它,那么它的格式就不对。这是一个格式不佳代码的简单示例。(如果这是你的编码风格,我先致歉)。
if (blnEditingInfo == false) { editButton1.Text = "Edit Customer"; } else { editButton1.Text = "Save Changes"; } //...
当它可以这样写时,它浪费了宝贵的屏幕空间。
editButton1.Text = !blnEditingInfo ? "Edit Customer" : "Save Changes"; //...
虽然使用“简写”的价值有很多争论,但我个人认为这是将几行代码压缩成一行代码的好方法。
别忘了生活
这条规则对我来说是最难遵守的。作为老板和员工,很容易把自己变成奴隶。请记住:“永远都有截止日期”。无论你多么努力地长时间工作来“赶超”,都会有另一个截止日期等着占用你的注意力。这很快就会导致“倦怠”,让你动力全无,创造力几乎为零!我的建议是每周给自己一天不用负责任的日子。听起来太多了?我向你保证,好处远远值得!如果你已经感到倦怠,请查看 这篇文章。如果你与之无法产生共鸣,说明你不在软件行业!
最终想法
还有很多其他的事情我们可以讨论,但我认为我们已经涵盖/揭示了一些重要的领域,这些领域将使你的编程生涯变得更好。作为一名独立开发者,你永远不知道何时会遇到需要你与第三方开发者合作的咨询工作。我们讨论的代码概念将为你(以及第三方)省去很多麻烦。
软件行业绝不是一条死胡同!总有新的东西可以发现,我欢迎你分享任何技巧。好了!我得走了。我要去处理最后一个话题了……