编程与软件开发
“编程”和“软件开发”之间区别的概述
引言
以下是我对“编程”和“软件开发”这两个术语的解释。(它们不一定是正确的语言定义。)
编程 | 创建执行特定任务的应用程序(工具)。 |
软件开发 | 创建专业、易用、可扩展、易于修改的应用程序。换句话说,设计良好。 |
这听起来可能有点自命不凡,这当然不是我的本意。让我把这一点说清楚,即“编程”是有用的,而且不是“愚蠢的”。换句话说,你可以例如“编程”一个非常复杂、尖端的算法。然而,当向公众提供一个应用程序时,人们可能会考虑升级或重写该已编程的应用程序,使其成为软件开发。
在结束介绍之前,我想说我们在某个时候都曾有过某种形式的(粗糙)编程。在这方面我绝非圣人。但是,我们应该意识到这一点,以便在必要时进行改进。
本文与任何编程语言无关,尽管示例是用 C# 编写的。它可以应用于任何编程语言,包括 HTML、XML、SQL、C++、Java、Python、脚本语言等。
背景
当我看到我们行业中的一些“应用程序”以及 CodeProject 上的一些代码片段时,我开始思考这篇文章。然而,真正的触发点是我听到同事们在走廊里谈论,说他们不喜欢软件开发人员,因为他们总是使用花哨的机器(用户没有这些机器),他们把事情搞得太复杂……不用说,我对这个说法(软件开发人员过时了?)有点恼火,尤其因为正是这些人“编程”。
第二个触发点是我需要向经理解释为什么某件事花了这么长时间,或者向招聘人员解释为什么我比其他人“更好”。
他们不改进应用程序的解释包括“不重要”、“它能达到预期目的”、“没多少人用”、“截止日期”等。
我认为属于“编程”的基本内容包括:
- 意大利面条式代码
- 无 N 层设计
- 不更改默认命名(
button1
、button1_Click
等) - 不考虑 UI 设计(图标、引导文本、一致性、控件位置逻辑等)
- 不考虑 UI 逻辑(制表符顺序、鼠标点击等)
- 不考虑可扩展性、可重用性等
- 无异常处理、日志记录等
- ...
但是,“编程”在原型设计或个人项目中可能非常有用,然后你可以剥离该应用程序中不必要的组件,进行清理,并将其集成到更大的软件开发项目中。(棘手的部分,找到构建你精美算法的正确“20”行代码已经完成了。)
Using the Code
为了说明我们正在努力澄清的内容,我们定义了一个应用程序请求。
创建一个应用程序,该应用程序将相加两个数字并显示结果。 |
在继续之前,我建议你自己动手完成,不要继续往下读。这是一个非常简单的应用程序。
... 编写代码,... 编写代码,... 编写代码,...
完成了?你可能没有做上面提到的事情。没关系,我们仍然继续写文章。
在此截图中,您将看到我们已经满足了请求。输入两个数字,然后按“相加”按钮。然后它计算这两个项的总和,并在第三个文本框中显示。
那么,仅凭截图来看,它有什么问题?
- 控件未对齐(足够细微,足以让人烦恼)
- 窗口标题是“
Form1
” - 窗口图标是标准的 Visual Studio 图标
但是,“编程”和“软件开发”之间的区别远不止显而易见的内容
- 制表符顺序完全错误
- 没有工具提示、没有引导文本等
- 窗口中有太多空白区域,控件的放置有点“随意”
- 它只能相加数字,就像请求的那样(仅此而已)
- 您可以手动更改实际的和
现在,纯粹为了好玩,尝试输入一个字母数字字符
糟糕...
为了进行比较,我们将查看同一个请求的“软件开发”版本。
在这里,我们看到同样的请求得到了满足,区别在于
- 所有字段都已对齐
- 您无需按按钮即可执行计算(需要更少的用户操作)
但是,不要做得过火。我个人讨厌那些为我做得太多的应用程序,因为这很难确定实际正在发生什么(应用程序是否做得正确?我有没有错过什么?……) - 窗口中的空白区域更少
- 有一些引导文本(并且不可见,但也有工具提示)
- 不可见,制表符顺序是逻辑的
- 不可见,添加了工具提示
- 添加了图标并更改了窗口标题
- 解决方案文本框是只读的,并且在其他任何文本框或组合框更改时都会重新计算
- 这个例子允许加、减、乘、除和取模。如果你认为客户不想要这个,那么你是对的,那么将其构建进去,并对用户隐藏其他选项。关键是,如果你预见到未来的请求(例如,请也添加减法),请确保能够轻松修改代码来实现这一点。
现在让我们尝试破坏它
第二个示例本身也可以改进,我们可以提供不允许任何字母数字字符的文本框,我们可以将文本框内容限制为一定数量的字符,我们可以允许对货币进行操作,我们可以允许十进制、八进制和二进制运算等等。
现在我们只陈述了屏幕上可见的显而易见的内容,让我们更深入地挖掘我们工作的“战壕”,看看代码。
编程示例的代码如下:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e) {
textBox3.Text = double.Parse(textBox1.Text) + double.Parse(textBox2.Text) + "";
}
private void button2_Click(object sender, EventArgs e) {
this.Close();
}
}
让我们总结一下这里有什么问题:
- 使用默认命名
- 无错误检查
- 硬编码的加法
- 无注释
现在让我们来看看软件开发的代码
GUI 类
public partial class BaseMathOperations : Form
{
public BaseMathOperations()
{
InitializeComponent();
ToolTip tt = new ToolTip();
tt.SetToolTip(txtbox_term1, "fill in term 1.");
tt.SetToolTip(txtbox_term2, "fill in term 2.");
tt.SetToolTip(txtbox_solution, "the outcome of the math.");
tt.SetToolTip(combo_operation, "mathematical operation");
tt.SetToolTip(btn_close, "close this application");
tt.Active = true;
combo_operation.SelectedIndex = 0;
}
private void txtbox_term_TextChanged(object sender, EventArgs e) {
Calculate();
}
private void combo_operation_SelectedIndexChanged(object sender, EventArgs e) {
Calculate();
}
private void btn_close_Click(object sender, EventArgs e) {
this.Close();
}
private void Calculate(){
//check if all three necessary fields are filled in.
if( !string.IsNullOrEmpty(txtbox_term1.Text) &&
!string.IsNullOrEmpty(txtbox_term2.Text) &&
combo_operation.SelectedIndex > -1){
switch(combo_operation.Text){
case "+": txtbox_solution.Text = SimpleMath.Add(txtbox_term1.Text, txtbox_term2.Text);
break;
case "-": txtbox_solution.Text =
SimpleMath.Substract(txtbox_term1.Text, txtbox_term2.Text);
break;
case "x": txtbox_solution.Text =
SimpleMath.Multiply(txtbox_term1.Text, txtbox_term2.Text);
break;
case "/": txtbox_solution.Text =
SimpleMath.Divide(txtbox_term1.Text, txtbox_term2.Text);
break;
case "%": txtbox_solution.Text = SimpleMath.Mod(txtbox_term1.Text, txtbox_term2.Text);
break;
} //end switch
} //end if
}
}
SimpleMath
类
public class SimpleMath {
public static string Add(string term1, string term2){
string retval = "";
try{
retval = Double2String(String2Double(term1) + String2Double(term2));
} //end try
catch(Exception ex){
retval = "not a number";
} //end catch
return retval;
}
public static string Substract(string term1, string term2){
string retval = "";
try{
retval = Double2String(String2Double(term1) - String2Double(term2));
} //end try
catch(Exception ex){
retval = "not a number";
} //end catch1
return retval;
}
public static string Multiply(string term1, string term2){
string retval = "";
try{
retval = Double2String(String2Double(term1) * String2Double(term2));
} //end try
catch(Exception ex){
retval = "not a number";
} //end catch
return retval;
}
public static string Divide(string term1, string term2){
string retval = "";
try{
retval = Double2String(String2Double(term1) / String2Double(term2));
} //end try
catch(DivideByZeroException dbzex){
retval = "cannot divide by 0";
} //end catch
catch(Exception ex){
retval = "not a number";
} //end catch
return retval;
}
public static string Mod(string term1, string term2){
string retval = "";
try{
retval = Double2String(String2Double(term1) % String2Double(term2));
} //end try
catch(Exception ex){
retval = "not a number";
} //end catch
return retval;
}
private static double String2Double(string val){
double retval = double.NaN;
if(!double.TryParse(val, out retval)){
retval = double.NaN;
} //end if
return retval;
}
private static string Double2String(double val){
string retval = "not a number";
if(val != double.NaN){
retval = val.ToString("0.00", CultureInfo.InvariantCulture);
} //end if
return retval;
}
}
这段代码肯定不完美,但肯定比第一个示例好很多。
- 注释 - ✓
- 错误处理 - ✓
- 逻辑划分(算术由另一个类处理)
- 无默认命名 - ✓
- 更好的 UI 设计逻辑,并且可以处理的不仅仅是加法。它还会格式化结果。
如果你现在说我不应该吞掉我的异常,你是对的。在源代码中,我在 SimpleMath
类顶部添加了一个注释。
/*
* Remarks:
* Normally you don't swallow the Exceptions as done here,
* but in order to keep the code simple, we don't handle them perse here.
*/
所以确实,你永远不应该吞掉异常,而应该将它们写入日志文件或数据库,以及/或者以某种格式化的方式将它们冒泡给用户。
这个第二个例子肯定比已编程的版本长,但如果你仔细看看,你会发现它一点也不复杂。SimpleMath
类可以重用,我们成功地分离了 GUI 和业务逻辑。(在实际应用程序中,您可能会将其拆分为不同的项目。)
最终,你花时间是为了节省时间(稍后)。
如何达到更高的标准
当你读这篇文章时,你可能没有那种感觉:“这真是启迪人心!”
然而,它在某种程度上确实是(我希望如此),为什么,既然我们如此清楚这一点,我们却一再犯错?(可能是因为我们懒惰)。
所以,这里有一些关于“软件开发”的建议:
#1
不要直接从需求开始编码,否则你会得到一个程序。相反,退后一步,从各个角度审视,设计你的应用程序(并为此花费时间)。
- 需求是否可能会在某个方向上扩展?
- 它是否需要与其他应用程序集成?
- 什么可以重用?
- ...
#2
遵守K.I.S.S. 原则。不要把事情搞得太复杂。如果你开始感到复杂,就开始重新思考。不要害怕重写你的代码片段,以便进一步简化开发。如果你预感到麻烦,就在它进一步恶化之前解决它。
#3
如果你需要实现一个复杂的算法,或者想检查一些特性(了解某个类如何工作),请创建一个新项目并使用“编程”来创建原型(尽管“编程”并不意味着你把所有“软件开发”规则都丢掉)。只有当你对你的解决方案充满信心时,才应该将其集成到最终版本中。直接在最终版本中这样做会很快导致混乱。
#4
定期清理代码,花时间浏览所有类,删除不必要的注释,或者寻找你可以做得更好、更简单的复杂代码。事实上,在读完这篇文章后立即这样做。
#5
非常重要:敢于质疑需求和/或你的老板!如果你看到了问题,或者有一个好主意,你应该沟通。(恕我直言,这才是你被雇佣的目的?)老板/客户通常有最终决定权,但你可以说“不”,如果他们真的、真的想要,好吧,你已经警告过他们了。
#6
测试。你自己或其他人。你不应该测试东西是否能工作(它很可能会),而是尝试让它崩溃!换句话说,不要测试预期的行为。例如,你可以通过用户测试的场景步骤,并对其进行轻微修改。我就是这样找到大多数 Bug 的,而且我们都知道用户从不遵守使用某事物的规则。
#7
我全力支持防御性编程。不要随便使用 try
/catch
块,并尽量从一开始就限制输入,沿途设置“安全门”。例如,整数输入可以通过 GUI 强制执行,可以在业务层和数据访问层进行检查,并且最终可以在数据库级别进行强制。
#8
正如下面评论的,不要允许在截止日期前最后一刻进行更改。设置一个适当的“代码冻结”期,在此期间(几乎)不允许对代码进行任何更改。对此要严格。这也是一个很好的时期来完成所有其他通常被遗忘的事情:文档。
#9
自动化一些用户工作是好的,但永远不要忘记用户仍然是他/她工作的负责人。永远不要让自己卷入经理想要完全自动化的系统的讨论中。你的代码会比一个不可能性驱动器带你去餐厅的速度更快地呈指数级增长。在许多情况下,最终用户仍然会抱怨,并且许多更改将不得不被撤销。
#10
你不必采纳每一个新出现的闪亮功能。
结论
#1
正如在背景部分所解释的,你可以利用本文的信息向你的经理或招聘人员提供信息。此外,你可以利用这些信息来更好地认识到如何做得更好,而且肯定更简单。最后,请注意,应用程序总是有改进空间的(花时间以后回顾你的工作),它总是可以变得更好,而且当有人问你“你为什么不这样做?”时,不要太快给出答案(可能是因为你不知道、不知道或者有某种原因)。
#2
做“编程”或“软件开发”可能意味着人们是否使用你的应用程序的差别。不要掉以轻心。
#3
你可以将这个概念进一步应用于研究、演示(!)、文档等。
关注点
当我写这篇文章时,我开始思考。什么时候一个应用程序被“编程”了,什么时候它被“软件开发”了?答案是:这是一个灰色地带。它随着经验的增长而增长,它来自你自己的观点。但有一点很清楚。你的代码总是有改进空间的,你应该批判性地对待自己的工作。
历史
- 2013/06/20:第一个版本
- 2013/12/09:第二个版本