关于本系列文字的来源,初衷和内容定位可以参考第一篇的开头部分,链接地址如下:
http://blog.csdn.net/binarytreeex/article/details/8174445
http://www.cnblogs.com/WideUnion/archive/2012/11/12/2766397.html
这里就不再重复了。本文的前一篇是讲程序员学习方面的话题,感兴趣的可以访问下面的连接:
http://blog.csdn.net/binarytreeex/article/details/8174445
http://www.cnblogs.com/WideUnion/archive/2012/11/12/2766397.html
问题解决篇主要讨论的是,在实际工作中解决问题的方法和心得。在我读大学的时候,我的老师曾说过工程师的责任就是解决问题。无论一个开发者的能力有多强,工作态度有多好,如果不能解决问题的话那么就什么都不是。所以能够解决实际问题是一个软件工程师的核心价值所在。本文分享内容的定位和第一篇中的一样依然不谈经典或者范例的东西,我相信这和大部分或者相当一部分的教科书,培训课程中的内容会有所区别。我想和同行们分享的仍然是我在实践中的体会与心得。这些内容主要侧重在解决问题的方法和思维技巧方面,所以本文不能解决某一个很具体的问题。比如,你不能在本文中找到如何实现分页查询的答案;如何给线程传参数的实现方法,等等。另外常规的一些所谓的解决问题的方法在本文中也是不讨论的,比如:论坛发帖,google或者百度,qq群提问,向老手提问,等等。当然这些方法也是有用甚至是最常用的,不讨论是定位的原因,不是我想否定这些方法的作用和价值。由于本文介绍的内容来自个人的实践,对于解决问题这样一个宏大的话题和全体开发者这样一个宽泛的群体来说,局限性和片面性是在所难免的。所以请同行们自行取舍,同时也要根据自己的经验,实际应用场合做出适当的变化,这样才能更好的应用本文介绍的内容。如果分享的内容可以为同行们解决实际工作中的问题起到积极作用的话,那么我的目的就达到了。当然如果能够达到庖丁解牛那样游刃有余的境界那是最好的。
作为问题解决篇的上半部分主要是针对新手来说的。我想讨论一下几个影响新手解决实际问题的因素,这些因素是我在实际工作中观察发现的。总体来说新手遇到的问题是比较简单的,因为一般情况下是不会把一个有难度的问题交给新手来解决的。因此新手的问题通常不应该被称作问题。我当然没有任何轻视新手的意思,只是针对问题本身而言的。基于这个前提新手注意改善一下自己的工作方法,从最终结果看是可以提高甚至显著提高解决实际问题的能力的。这些因素主要是以下三个:
1. 不清楚或者不知道自己在做什么
2. 实现功能不到位
3. 程序调试不通
下面开始依次讨论这三个问题
一.不清楚或者不知道自己在做什么
能力差的一个表现是花了时间解决问题,但是最终没有解决。造成这一结果的原因当然是多种多样的,其中有一部分是新手选择了完全错误的方法或方向来解决问题。对于选择的方法或方向不能解决问题这一点来说,新手往往完全没有任何感觉,给人的第一印象是对于自己当下在做的事情意味着什么,往往不清楚甚至完全不明白。对需要解决的问题本身的理解应该也是一个原因。如果是这样的话那么这个解决方法还是相对很简单的,那就是每次接到任务或者准备解决问题时,需要确认自己是否理解清楚了。但是更多的情况却不是由于对问题本身的理解引起的,那么这个时候怎么办呢?
首先需要确立一个信念:上级安排给新手的问题一般来说都是简单的,容易解决的。所以不要慌张,要有必胜的信心。其次,在动手前先思考一下我将打算如何解决这个问题。当然由于经验的匮乏和各人能力的差异,这种思考在相当的情况下很有可能是效果不太理想的,但是我还是建议坚持这么做,次数多了能力就慢慢来了。第三点需要确认自己解决问题所需要的信息都了解清楚了。新手工作时间不长接触的东西都是以前没有了解的。特别是涉及到具体于项目相关的内容更是如此。而解决一个实际问题不可能就是一个孤立的问题,需要解决的问题往往会和其它已存在的内容是有联系的。而这些有联系的内容往往是新手不了解的。所以新手需要确认这些内容自己已经了解了,否则就需要向上手询问了,这个是合情合理的提问。
我举一个简单例子来说明一下。我有一次面试被要求直接修改一个界面上的功能,是在真实的项目中修改。当打开solution后发现里面有很多工程和N多的窗体,于是第一个面对的问题就是需要修改的位置在哪里?我见到过的新手很可能会有以下一些做法:
1. 在整个solution中慢慢找,而且找了N个小时后也不怀疑自己是否可以找到。
2. 执行程序,找到主窗体(这多少是一个进步),然后在主窗体中改。至于是不是应该在主窗体中改就不考虑了。如果没有在主窗体中找到需要改的内容,那么自己就加进去然后实现这个功能。
3. 没有做什么实际事情,可能是在发呆,可能是想了一会又开一会小差,如此不断交替。
4. 从自己看到的第一行代码开始读代码,然后试图了解其中的逻辑,其思路是不读懂代码如何修改呢?
5. …
第一点就是方法选择错误。其错误不是在于找那个窗体,而是找了N个小时后还在找。假设给你一封信,上面没有地址,只是告诉你内容然后要求你去给某一个和信的内容匹配的人。那么你会从一个城市的第一条街道的第一个门牌开始依次把整个城市遍历一遍么?另一个问题是没有从自己尝试的实践活动中得到的反馈来修改自己的行为或者决策。给人的印象就是不知道自己做的事情意味着什么,对能不能完成任务一点感觉也没有。第二点是属于不动脑经的作法,要求是改程序而不是新实现一个功能。并且加入代码的位置是否正确也不考虑,这是对自己所做的事情完全没有感觉。第三点是比较糟糕的,应该尽快改变。第四点方法和思路都有问题。新手不妨评估一下自己的读程能力,如果认为自己足够强那还可以不妨一试,否则就不应该这么做。可以说这种新人对自己能力的认识都有问题。实际上,有足够读程能力的人一般是不会那么盲目的就开始读程的。敢这么做的基本上都是读程能力比较差的。
我说一下自己的作法,当然我不是说这个就是标准答案,只是介绍一个可供选择的方法以供参考。修改功能的第一步是需要知道在哪一个窗体中修改,而面对那么多的窗体最佳的选择就是提问,请熟悉项目的人告诉我那个窗体在哪里。原因如下:
1. 我不了解项目,我是在一无所知的情况下开始的,这样的提问是合情合理的。当然我是大致看了那些窗体的,但是发现自己找出来大有难度。
2. 我当时状态是在上机笔试,我不能把有限的时间花费在找窗体上。
第二个理由就我的例子来说更强一些。当然实际工作的情况会有所不同,所以大家需要根据自己的具体情况选择不同的作法。但是一条原则是把主要的精力和时间用在解决主要矛盾上,不要被次要或者支流问题分散了注意力。我通过询问就将解决问题需要的相关信息获取了,这是能够解决问题的基础。第二步是打开那个正确的窗体根据要求修改代码,那么这时又有一个问题,需要修改的代码在哪里,或者说应该在哪里改代码?那么这时是不是仍然可以使用上面的两个理由再次提问呢?就我的情况来说就不能再提问了,理由如下:
窗体中的代码不太复杂,应该可以独立解决。这个事实是在读了代码以后才知道的。所以新手可以尝试去实践一下,关键是要根据实践得到的反馈结果及时修改自己的方法,采取最佳途径解决问题。如果当时我基于上述的两个理由不去读代码而直接再次提问,那么面试你的人是知道那些代码的复杂程度的,所以他就很有可能会认为我的能力有问题了。因此提问要注意分寸和度。新人可以以这个例子作为参考,当然自己具体遇到场合会有所不同,所以要结合自己的具体情况作判断。这里顺便提醒新手,不要在自己不理解的情况下在真实项目中擅自修改别人写的代码,这是很不明智的作法。如果实在要改,那么在改的地方调用一个方法,自己想写的代码写在那个方法中,然后给那个方法和调用该方法的地方写上注释和日期。对于我遇到的这个问题来说,逻辑不是很复杂,看一下代码也就能够确认位置了。那时我是一个有工作经验的程序员,但是作为上机测试题目还是给出了一个不太难的问题,这说明一般来说不会给新手太难的问题,所以应该要有信心。然后我就在相关的位置修改代码,实现要求的功能。
接着又来一个新的问题,面对那么多的工程,如何执行程序?执行后如何操作才能执行到我需要调试的代码呢?这也是解决问题需要的相关信息。没有这些信息,程序无法调试,那么问题也就无法解决。改完代码只是改完,调试通过才能算是解决问题。这个问题的处理原则上面提到了,这里可以再给一个量化的尺度,以便实践中执行。假设修改代码花费了30分钟,这是解决主要矛盾花费的时间。那么解决次要问题的时间原则上应该小于这个时间,或者远小于这个时间。具体我的例子来说,如果尝试执行程序花费了30分钟还没解决,那就是方法选择有问题了。所以不要花费过多的时间在次要矛盾上,不要无谓的浪费时间。自己找可能需要一小时,了解的人告诉你只需要几秒钟。另外需要注意,在类似的这些场合新人要习惯提问,敢于提问,但是问了之后需要记住,同样的或者同类型的问题最好不要重复提问。对于这个上机笔试问题我的解决过程是这样的:我看了一下那个窗体所在的工程,发现不是默认启动执行的工程。所以就将这个项目设置为默认启动。新手注意,这时修改了solution的设置,最好记住原来的启动工程是哪一个,以便改回来。然后就是执行那个工程。结果是程序抛异常无法执行,说是初始化失败。
这是遇到的第四个问题,现在是应该尝试解决这个问题还是直接提问?就我的例子来说是直接提问,理由如下:
1. 解决初始化失败这个问题超出了笔试题考查的范围
2. 我对那个工程不了解,初始化中的逻辑有多复杂不确定,所以是否可以在笔试规定的时间内(实际上时间没有明确规定,但是一般主观上会有一个可接受的范围)完成没有把握
3. 退一步讲,我能找到初始化失败的原因;再退一步讲,我还有能力和时间解决初始化抛异常的问题,那么我真就去fix那个bug么?我的回答是:不。因为我不是该公司的员工,我不应随便修改那些没有要求我改的代码。
第三条理由在这个场合是最强的。新人可以结合自己的在公司中遇到具体情况灵活的确定自己的决策。于是我就将默认启动工程改回来,然后提问要求告知如何操作才能执行到自己修改的代码。在获得这些与解决问题相关的信息后,这个调试工作很快就完了,上机笔试题也就顺利做完了。由于是在真实项目中修改的,所以一个细节是临走时告知相关人员我修改的代码的位置和新增的方法。
新手可以体会一下,什么时候提问什么样的问题,以及什么事情自己可以去尝试解决,尝试的度的把握;什么事情自己不要求尝试解决。我相信这一点上部分有工作年份的开发者或多或少也会有的。如果把这个问题扩展一下,其应用的场合可以扩展到对问题的主要矛盾的辨识上,进而可以应用在解决更复杂的问题上。
2.功能实现不到位
经验的不足,业务知识的匮乏是我观察到的新手实现功能不到位的主要原因。当然出现这种问题也是正常的,而且也不是什么严重的问题或者过错,简单提一下在工作中注意避免就可以了。下面列举一下几种不到位的情况:
a.如果要求实现用户注册功能,并且没有给出具体需求,那么一个细节是:要求用户重复输入一遍密码是应该实现的。以目前的使用习惯没有明确告知做或者不做,而程序员事实上没有做出来就可以理解为功能实现不到位。
b.窗体界面上控件的大小,颜色,字体是否和已存在的控件一致。比如窗体上已有按钮控件的大小都是一样的,那你新加的按钮大了点或者小了一点,那就要注意了。
c.操作习惯是否一致。比如,控件内容填充是否联动;鼠标移动到控件上面是否有选中效果;错误信息显示位置等等。
d.编码风格,注释书写格式是否一致等等。
再举一个我在实际工作上遇到的一个例子。一个新手实现完成了一个用户登录界面及其中的逻辑。但是如果由于忘记密码或者其它原因导致无法登录成功时,登录界面无法关闭,用户无法完成其它操作,比如退出软件。这是一个真实案例,而且在我提出不完善的地方后,那个新人还拒绝修改。我最后是用一起玩过的暗黑登录战网的例子才让他明白功能上的缺陷。主体上登录功能是实现的,也测试通过了,这个没有问题。但是遗留一个无法退出登录操作问题,这就是功能实现不到位,或者说不完整。在真实开发中,这和没有完成功能基本上是等价的。
另外一个造成实现功能不到位的原因是缺乏质量意识和产品意识。这个错误我是犯过的,在一个外包公司工作时就有这个问题。那是第一次开发有用户可以直接操作的界面的软件产品。之前的想法总是关注在功能是否实现上,而忽略了上面提到的那些注意点,所以我开发的模块没有通过测试。注意,当时我是有六年开发经验,还有高程证书的,绝对不是一个新手。这里强调一下,希望新手注意。
三.程序调试不通
从整体上来说,调通程序对新手是一个坎。能够顺利的调通程序是开发者可以独立工作的一个重要考察标志。回想自己学习C语言的时候,还是吃了点苦头才走过来的。先谈一下编译错误,主要是两个:语法错误和链接错误。
对于编译中的语法错误,可以选择出现在前面的错误先解决。这是因为编译器在检查代码的语法错误时,有可能前面的语法错误导致后面的错误。这时先尝试解决后面的语法错误,就不太明智,难度相对就会大点。所以建议解决语法错误时从第一个或者靠前面的开始。另一个策略是先解决容易的语法错误。有时候出现语法错误很多,看着是比较烦的,这时可以先把简单的明显的错误解决掉。比如,看到书上的一段代码,想试一下,于是将代码敲进去然后编译。这时出现语法错误就比较多了,那么可以将类似标识符未定义(产生这个错误的原因可能是敲代码时的手误)之类的错误先解决掉。这样就可以逐步减少错误的数量,从而可以让我们的注意力能更好的集中在难度大的错误上。从总体上来说,语法错误的排查解决应该算是一个比较容易处理的问题。以当前流行的开发工具来说,对语法错误的排查提供了越来越好的支持,所以即便是新手我还是建议能够靠自己的能力完全排查掉语法错误。
相对于语法错的简单而言,排查编译时的链接错误的难度就会大一点了。在C或者C++这类编译器中,没有将必要的库文件引用到项目中是产生链接错误的一个频率很高的原因。当出现链接错误时,可以先肯定一点,源代码本身已经没有问题了。因为代码只有在通过词法和语法检查才会编译生成代码,所以产生链接错误时,代码本身的出问题的可能性就不大了。这个线索可以告诉我们解决问题方向就不要在源代码本身去找了。需要注意一点,对于编译时的错误,一定要看清错误的信息,明确错误信息说的是什么然后再去排查错误,切记。因为很多的情况下错误信息就直接给出了答案的。举一个我在开发EntityModelStudio时自己遇到的问题。我在一个工程里引用了另一个工程的dll,但是由于操作问题,实际引用的dll是另一个位置的不同版本的同名dll文件。产生的后果是dll工程调试什么问题都没有,但是引用后调试时怎么做都是错的。新手可以体会一下实际问题是不太按规矩出牌的。
除了编译错误,另外一类就是所谓的逻辑错误了。这才是开发者调试程序的重点。实际上在很多场合两,三个小时排除几百,上千的语法错误都不算什么,所以排除实现不了指定功能的逻辑错误才是核心问题。逻辑错误的具体表现是程序执行的结果达不到预期的结果,或者只是部分达到要求而不是完全符合要求。造成这个结果的原因当然是各种各样的,可能是选择算法有问题,调用的方法不正确,等等。对于这个问题我给出的建议是请掌握调试程序的基本技巧,就是断点设置和变量值的查看。
断点的设置可以从发现出错位置的方法的开始处设置,或者某一个肯定在出错位置前面的地方,当程序停在断点后按F10(Step Over的意思)逐步跟踪代码。当发现执行某一行代码执行完时结果不对,那么就要查看这行代码了。如果是单一的代码那么根据错误的结果修改代码,如果是方法调用,那么就要进入到这个方法的内部(按F11,Step Into意思),使用相同的办法继续跟踪。用这个办法可以逐步逼近错误的位置。有时为了能提高跟踪代码的效率,在自己对错误和代码比较熟悉的情况下,可以在某些关键点设置断点,查看程序的执行情况。这个事情听上去比较简单,但是遇到一些新手却会犯这么一个错误。F10和F11的功能是清楚的,但是在看到方法调用后结果不对时,却不知道要进入该方法的内部继续跟踪代码。这里提醒新手,F10和F11就是这么交替使用的,直至定位到最终出错的位置。如果仅仅使用这样的方法就可以定位并排除错误,那么这样的错误还是比较容易的,有可能是最容易的情况。下面介绍稍微有一点难度的情况。
有时候我们可能需要写一点代码(这些代码还不能算是测试代码),为使用断点设置这个调试手段起到辅助的作用。比如,错误的位置是在一个循环体内。如果我们直接简单的在循环体内设置断点,那么循环执行的第一次就停在断点处,然后开始单步跟踪。这时就会有一个问题,这个循环要跟踪多少次,才能等到出现错误的那次循环呢?如果是第一次,或者前几次那还好,如果是100次,200次或者更多次那就麻烦了。对于这样的场合就需要写点代码来辅助调试了。假设循环是for循环,那么在循环体内的恰当位置写下类似下面的代码:
for (int i = 0; ...; i++)
{
// 这些是需要调试的代码,已经存在的,假设有若干行
...
// 这个if语句是需要加入的调试代码
if (i == 100)
{
int a = 10; // 断点设置在这行代码上
}
// 这些是需要调试的代码,已经存在的,假设有若干行
...
}
调试代码起到的效果是,在循环到第101次时程序就会停在断点处了。这可以显著提高调试的效率。还有一些场合我们调试的程序和Windows的消息有关,这个时候断点的设置位置和时机就会比较麻烦。比如,当需要把断点设置在鼠标事件中时或者OnPaint事件时,就会这样。因为直接设置的话,那么每次鼠标事件或者OnPaint事件触发时都会导致程序停下来。而这时还远远没到错误出现的时候。这时可以考虑先不设置断点,等到最后一步操作前再设置断点。比如,需要将用户输入的数据在OnPaint事件中显示在客户区。那么可以考虑在恰当的位置执行一行刷新客户区的代码(对于C#的窗体是Invalidate,对于MFC还可以考虑发消息),断点先设置在那行刷新的代码上。等程序执行停在那行代码时,再在OnPaint方法中设置断点,然后按F5,这样就可以让OnPaint中断点直接停在我们需要的时刻上。再给出一个可供选择的方案是,如果可能,将OnPaint中的代码拿出来,放到按钮的点击事件中,这样调试就可以避开原来的麻烦了。更一般的思路是,在程序中加入调试代码,一般是一个if语句。该语句的条件表示了你希望程序停下来单步跟踪的时刻,然后将断点设置在这个if语句内部的代码上就可以了。
有些场合可以考虑使用控制台输出信息的办法。当然也可以选择写文件,作用是一样的,但是对于Visual Studio开发环境来说控制台输出对调试程序更为方便些。在程序的特定位置写入一些输出信息到控制台的代码,C#中我用Console类的WriteLine方法,或者Debug类的同名方法。这样程序的执行不会被打断,同时又能看到必要的信息。这是一个很不错的优点。在调试的时候,如果觉得有困难,错误位置无法确定,那么我建议可以采取逐步解决的办法。先实现最简单的情况,然后调试通过,接着再实现下一个情况,然后再调试通过。如果可能这里我想强调一下,这里分步依次实现的情况最好能够做到独立。比如,代码可以用明显的if else语句或者switch语句的分支隔开,或者代码放在不同的方法中。这样在调试程序时可以让我们每次只关注在一个情况上,并且处理不同情况的代码至少在视觉上没有相互干扰,这对我们解决问题是有帮助的。这个方法在很多场合是很有效的,用好的话还是简化问题的方法。在开发EntityModelStudio的时序图时我采用的就是这个方法,所不同的是时序图的各种操作行为的分类计数对新手来说是一个有点难度的问题。
四.两个有用的技巧
再介绍两个个人认为很有用的技巧,那就是对比法和关键点查找,这是我在工作第一年维修家电时积累的经验,实践发现在软件开发中也是有用的。所谓对比法就是手上有一份代码(或者例子)可以实现要求功能,而我们现在需要实现相同的功能,那么我们就可以对照着例子改,直至实现需要的功能。听上去很简单,事实上这个方法的表述也确实很简单,也许会有人觉得这和google或者百度后的copy/paste有区别么?应该说都会用到copy/paste,这点相同,但是思路上不同,这是区别。我用前几天在CSDN上看到的一个帖子作为例子来说明。
帖子中的有如下的代码(不是原文,但是意思相同):
public class MyClass
{
int Age {get; set;}
}
帖子的问题是:给MyClass对象的Age属性赋值,提示出错。这当然是一个很新手的初级问题,直接加上public修饰符就可以解决问题。下面试一下用对比法来解决。
问题的现象是不能通过对象访问属性,而事实是应该可以访问,那么就要试图去找一个例子,而那个例子是可以通过对象访问属性的。这样的例子上网很容易找,然后对比差异,应该很快发现差一个public,加上就可以了。这个和copy/paste还是接近的。但是有的时候找不到这个例子怎么办?那么我们可以通过对比别的内容来尝试解决问题。比如,现在问题是通过对象访问不了属性,那么可以通过对象访问方法么?如果找到通过对象可以访问方法的例子,那么就可以考虑通过调用方法的例子来修改属性。这个就是思维技巧的差异了,要灵活应用方法。在加大一点难度,那么上不了网怎么办?这时可以考虑通过现有工程中已有的代码作为例子。这个问题的难度应该说不大,但是思考的步骤对于新手来说应该是有点挑战的。从解决问题的思考技巧来说,如果一个新手自己就可以有这样的思路,那我就认为这个新手是有才的。希望新手可以体会一下。不同的技巧使用对比法可以在更为复杂的情况下解决难得多的问题,这部分在下篇讨论了。
从我个人的经验来说,新手在学习开发或者一个开发者开始一个新的开发方向的时候,典型的就是使用一门新的语言,会遇到一些无法用常理可以解释的问题。比如我自己的第一个Windows程序。我用了很短时间将30行左右的代码敲入计算机,但是却用了几乎整整八天的时间才调试通过。这个Hello World级别的程序,最终查出的原因是工程名不对。我用的是abc,改成aaa就可以了。这类问题在此后的开发经历中也遇到过,但是总体是越来越少,解决问题所耗费的时间越来越短。但是其共同的特征是无法用常理解释或者莫名其妙的自己消失了。遇到这类问题时,首先确保当前的工程足够简单。如果没有足够简单的工程,可以考虑新建一个。然后可以尝试逐步注释代码找出问题原因。或者先构造一个足够简单并可以通过调试的程序,然后一步一步的修改朝目标逼近。在这一过程中哪一步修改出了问题,那么问题就在那一步上。当然最好每逼近一步就做一次备份。总体上来说这个方法也是属于对比法的范畴。
所谓关键点查找是指程序执行在时间上是顺序的,由此总体上实际的代码也是顺序执行的。那么一旦程序出现问题,我们就可以把程序在出问题的点上分成两部分,出问题之前的和出问题之后的。如果我们看到的结果是正确的,那么问题点应该在当前时间点后执行的代码中,注意我说的是时间点的先后,不是源代码物理位置上的先后。所以我们应该在那些代码中去查找,并在恰当的位置设置断点。前面提到的F10和F11也是这个意思的具体表现。这里再次强调这个思路的意思是,希望新手在遇到问题而困惑时这个思考的技巧可以帮助自己理顺思路,而不是仅仅把F10和F11的作用看成是两个按键对应的功能。
四.给新手的建议
好了,这篇博文大致就写到这里,感觉谈的内容对新手来说可能有点多了。这些方法和技巧在使用中我相信会对新手是有帮助的。但是任何方法的应用都需要一个逐步熟练的过程,就像人的成长一样。所以新手也不能抱着一口吃成一个胖子的心理。务实的做好自己的工作,在实践中逐步提高和进步才是正确的作法。另外也要对自己有信心。其实新手还是有不少优势的,比如学习速度快,适应能力强,什么事情都有热情去做或者愿意做,面对困难有冲击力,这些都是长处。所以面对经验缺乏,能力相对较差的(较差这个词可能用得不好)情况用不着着急或者影响心情。总之尽力而为了就可以了,今天比昨天好,这次比上次好就行了。
五.提问解答
最后我解释一下学习篇中被提问到的一个问题。学习篇中我提到读一本书最好是用20到40分钟能过一遍。有人提问做起来有困难,那么在此解释一下。首先我知道的快速阅读技巧有两个。第一个是正统的快速阅读法,据说斯大林用这个方法可以在四个小时内看完一本五百页的书。通常的阅读方法是用视力最清楚的那个点一次看一个字,快速阅读方式是要求读者用眼睛一次看一段字,比如:三个,七个之类的。这样就会比通常的阅读方法快三倍或者七倍。这个方法是需要练习才能掌握的。第二个方法实际上是一个技巧。那就是只阅读每一段的第一句话和最后一句话,这个方法据说可以用10%的时间获得50%的信息量。这个技巧我没有试过,效果如何就不清楚了。其次,在读计算机书时,可以用视线扫描文字,这个不是快速阅读,这比快速阅读还要快很多。然后用视线滤出敏感的词汇,发现是重点的,感兴趣的或者不懂的就停下来看一下。另外已有的开发经验也至关重要。比如,阅读ADO开发数据库的书,我就直接用视线扫面书上的例子代码,ADO的初始化也看了一下,然后关注使用的步骤和其中的方法名。其原因是在这之前我有用C++使用ODBC的开发经验。另外需要注意这个方法对新手可能不太适用,因为新手经验少,调试程序的能力有限。新手看懂代码不等于能调通程序,所以新手一定要上机敲代码试一下才算会。但是老手就不用了,足够的开发经验可以保证这类入门级的例子,看懂就能调通。所以开发经验在读书时也是有用的。另外一个需要澄清的问题是本系列的文字与任何培训机构没有任何关系。如果还有别的问题或者愿意交流的可以加入我的群:231233168。
下一篇还是讨论解决问题的方面的内容,但是难度会增加很多,题目应该是问题解决篇(下)。这是针对有一定开发经验的开发者的。
http://blog.csdn.net/binarytreeex/article/details/8174445
http://www.cnblogs.com/WideUnion/archive/2012/11/12/2766397.html
这里就不再重复了。本文的前一篇是讲程序员学习方面的话题,感兴趣的可以访问下面的连接:
http://blog.csdn.net/binarytreeex/article/details/8174445
http://www.cnblogs.com/WideUnion/archive/2012/11/12/2766397.html
问题解决篇主要讨论的是,在实际工作中解决问题的方法和心得。在我读大学的时候,我的老师曾说过工程师的责任就是解决问题。无论一个开发者的能力有多强,工作态度有多好,如果不能解决问题的话那么就什么都不是。所以能够解决实际问题是一个软件工程师的核心价值所在。本文分享内容的定位和第一篇中的一样依然不谈经典或者范例的东西,我相信这和大部分或者相当一部分的教科书,培训课程中的内容会有所区别。我想和同行们分享的仍然是我在实践中的体会与心得。这些内容主要侧重在解决问题的方法和思维技巧方面,所以本文不能解决某一个很具体的问题。比如,你不能在本文中找到如何实现分页查询的答案;如何给线程传参数的实现方法,等等。另外常规的一些所谓的解决问题的方法在本文中也是不讨论的,比如:论坛发帖,google或者百度,qq群提问,向老手提问,等等。当然这些方法也是有用甚至是最常用的,不讨论是定位的原因,不是我想否定这些方法的作用和价值。由于本文介绍的内容来自个人的实践,对于解决问题这样一个宏大的话题和全体开发者这样一个宽泛的群体来说,局限性和片面性是在所难免的。所以请同行们自行取舍,同时也要根据自己的经验,实际应用场合做出适当的变化,这样才能更好的应用本文介绍的内容。如果分享的内容可以为同行们解决实际工作中的问题起到积极作用的话,那么我的目的就达到了。当然如果能够达到庖丁解牛那样游刃有余的境界那是最好的。
作为问题解决篇的上半部分主要是针对新手来说的。我想讨论一下几个影响新手解决实际问题的因素,这些因素是我在实际工作中观察发现的。总体来说新手遇到的问题是比较简单的,因为一般情况下是不会把一个有难度的问题交给新手来解决的。因此新手的问题通常不应该被称作问题。我当然没有任何轻视新手的意思,只是针对问题本身而言的。基于这个前提新手注意改善一下自己的工作方法,从最终结果看是可以提高甚至显著提高解决实际问题的能力的。这些因素主要是以下三个:
1. 不清楚或者不知道自己在做什么
2. 实现功能不到位
3. 程序调试不通
下面开始依次讨论这三个问题
一.不清楚或者不知道自己在做什么
能力差的一个表现是花了时间解决问题,但是最终没有解决。造成这一结果的原因当然是多种多样的,其中有一部分是新手选择了完全错误的方法或方向来解决问题。对于选择的方法或方向不能解决问题这一点来说,新手往往完全没有任何感觉,给人的第一印象是对于自己当下在做的事情意味着什么,往往不清楚甚至完全不明白。对需要解决的问题本身的理解应该也是一个原因。如果是这样的话那么这个解决方法还是相对很简单的,那就是每次接到任务或者准备解决问题时,需要确认自己是否理解清楚了。但是更多的情况却不是由于对问题本身的理解引起的,那么这个时候怎么办呢?
首先需要确立一个信念:上级安排给新手的问题一般来说都是简单的,容易解决的。所以不要慌张,要有必胜的信心。其次,在动手前先思考一下我将打算如何解决这个问题。当然由于经验的匮乏和各人能力的差异,这种思考在相当的情况下很有可能是效果不太理想的,但是我还是建议坚持这么做,次数多了能力就慢慢来了。第三点需要确认自己解决问题所需要的信息都了解清楚了。新手工作时间不长接触的东西都是以前没有了解的。特别是涉及到具体于项目相关的内容更是如此。而解决一个实际问题不可能就是一个孤立的问题,需要解决的问题往往会和其它已存在的内容是有联系的。而这些有联系的内容往往是新手不了解的。所以新手需要确认这些内容自己已经了解了,否则就需要向上手询问了,这个是合情合理的提问。
我举一个简单例子来说明一下。我有一次面试被要求直接修改一个界面上的功能,是在真实的项目中修改。当打开solution后发现里面有很多工程和N多的窗体,于是第一个面对的问题就是需要修改的位置在哪里?我见到过的新手很可能会有以下一些做法:
1. 在整个solution中慢慢找,而且找了N个小时后也不怀疑自己是否可以找到。
2. 执行程序,找到主窗体(这多少是一个进步),然后在主窗体中改。至于是不是应该在主窗体中改就不考虑了。如果没有在主窗体中找到需要改的内容,那么自己就加进去然后实现这个功能。
3. 没有做什么实际事情,可能是在发呆,可能是想了一会又开一会小差,如此不断交替。
4. 从自己看到的第一行代码开始读代码,然后试图了解其中的逻辑,其思路是不读懂代码如何修改呢?
5. …
第一点就是方法选择错误。其错误不是在于找那个窗体,而是找了N个小时后还在找。假设给你一封信,上面没有地址,只是告诉你内容然后要求你去给某一个和信的内容匹配的人。那么你会从一个城市的第一条街道的第一个门牌开始依次把整个城市遍历一遍么?另一个问题是没有从自己尝试的实践活动中得到的反馈来修改自己的行为或者决策。给人的印象就是不知道自己做的事情意味着什么,对能不能完成任务一点感觉也没有。第二点是属于不动脑经的作法,要求是改程序而不是新实现一个功能。并且加入代码的位置是否正确也不考虑,这是对自己所做的事情完全没有感觉。第三点是比较糟糕的,应该尽快改变。第四点方法和思路都有问题。新手不妨评估一下自己的读程能力,如果认为自己足够强那还可以不妨一试,否则就不应该这么做。可以说这种新人对自己能力的认识都有问题。实际上,有足够读程能力的人一般是不会那么盲目的就开始读程的。敢这么做的基本上都是读程能力比较差的。
我说一下自己的作法,当然我不是说这个就是标准答案,只是介绍一个可供选择的方法以供参考。修改功能的第一步是需要知道在哪一个窗体中修改,而面对那么多的窗体最佳的选择就是提问,请熟悉项目的人告诉我那个窗体在哪里。原因如下:
1. 我不了解项目,我是在一无所知的情况下开始的,这样的提问是合情合理的。当然我是大致看了那些窗体的,但是发现自己找出来大有难度。
2. 我当时状态是在上机笔试,我不能把有限的时间花费在找窗体上。
第二个理由就我的例子来说更强一些。当然实际工作的情况会有所不同,所以大家需要根据自己的具体情况选择不同的作法。但是一条原则是把主要的精力和时间用在解决主要矛盾上,不要被次要或者支流问题分散了注意力。我通过询问就将解决问题需要的相关信息获取了,这是能够解决问题的基础。第二步是打开那个正确的窗体根据要求修改代码,那么这时又有一个问题,需要修改的代码在哪里,或者说应该在哪里改代码?那么这时是不是仍然可以使用上面的两个理由再次提问呢?就我的情况来说就不能再提问了,理由如下:
窗体中的代码不太复杂,应该可以独立解决。这个事实是在读了代码以后才知道的。所以新手可以尝试去实践一下,关键是要根据实践得到的反馈结果及时修改自己的方法,采取最佳途径解决问题。如果当时我基于上述的两个理由不去读代码而直接再次提问,那么面试你的人是知道那些代码的复杂程度的,所以他就很有可能会认为我的能力有问题了。因此提问要注意分寸和度。新人可以以这个例子作为参考,当然自己具体遇到场合会有所不同,所以要结合自己的具体情况作判断。这里顺便提醒新手,不要在自己不理解的情况下在真实项目中擅自修改别人写的代码,这是很不明智的作法。如果实在要改,那么在改的地方调用一个方法,自己想写的代码写在那个方法中,然后给那个方法和调用该方法的地方写上注释和日期。对于我遇到的这个问题来说,逻辑不是很复杂,看一下代码也就能够确认位置了。那时我是一个有工作经验的程序员,但是作为上机测试题目还是给出了一个不太难的问题,这说明一般来说不会给新手太难的问题,所以应该要有信心。然后我就在相关的位置修改代码,实现要求的功能。
接着又来一个新的问题,面对那么多的工程,如何执行程序?执行后如何操作才能执行到我需要调试的代码呢?这也是解决问题需要的相关信息。没有这些信息,程序无法调试,那么问题也就无法解决。改完代码只是改完,调试通过才能算是解决问题。这个问题的处理原则上面提到了,这里可以再给一个量化的尺度,以便实践中执行。假设修改代码花费了30分钟,这是解决主要矛盾花费的时间。那么解决次要问题的时间原则上应该小于这个时间,或者远小于这个时间。具体我的例子来说,如果尝试执行程序花费了30分钟还没解决,那就是方法选择有问题了。所以不要花费过多的时间在次要矛盾上,不要无谓的浪费时间。自己找可能需要一小时,了解的人告诉你只需要几秒钟。另外需要注意,在类似的这些场合新人要习惯提问,敢于提问,但是问了之后需要记住,同样的或者同类型的问题最好不要重复提问。对于这个上机笔试问题我的解决过程是这样的:我看了一下那个窗体所在的工程,发现不是默认启动执行的工程。所以就将这个项目设置为默认启动。新手注意,这时修改了solution的设置,最好记住原来的启动工程是哪一个,以便改回来。然后就是执行那个工程。结果是程序抛异常无法执行,说是初始化失败。
这是遇到的第四个问题,现在是应该尝试解决这个问题还是直接提问?就我的例子来说是直接提问,理由如下:
1. 解决初始化失败这个问题超出了笔试题考查的范围
2. 我对那个工程不了解,初始化中的逻辑有多复杂不确定,所以是否可以在笔试规定的时间内(实际上时间没有明确规定,但是一般主观上会有一个可接受的范围)完成没有把握
3. 退一步讲,我能找到初始化失败的原因;再退一步讲,我还有能力和时间解决初始化抛异常的问题,那么我真就去fix那个bug么?我的回答是:不。因为我不是该公司的员工,我不应随便修改那些没有要求我改的代码。
第三条理由在这个场合是最强的。新人可以结合自己的在公司中遇到具体情况灵活的确定自己的决策。于是我就将默认启动工程改回来,然后提问要求告知如何操作才能执行到自己修改的代码。在获得这些与解决问题相关的信息后,这个调试工作很快就完了,上机笔试题也就顺利做完了。由于是在真实项目中修改的,所以一个细节是临走时告知相关人员我修改的代码的位置和新增的方法。
新手可以体会一下,什么时候提问什么样的问题,以及什么事情自己可以去尝试解决,尝试的度的把握;什么事情自己不要求尝试解决。我相信这一点上部分有工作年份的开发者或多或少也会有的。如果把这个问题扩展一下,其应用的场合可以扩展到对问题的主要矛盾的辨识上,进而可以应用在解决更复杂的问题上。
2.功能实现不到位
经验的不足,业务知识的匮乏是我观察到的新手实现功能不到位的主要原因。当然出现这种问题也是正常的,而且也不是什么严重的问题或者过错,简单提一下在工作中注意避免就可以了。下面列举一下几种不到位的情况:
a.如果要求实现用户注册功能,并且没有给出具体需求,那么一个细节是:要求用户重复输入一遍密码是应该实现的。以目前的使用习惯没有明确告知做或者不做,而程序员事实上没有做出来就可以理解为功能实现不到位。
b.窗体界面上控件的大小,颜色,字体是否和已存在的控件一致。比如窗体上已有按钮控件的大小都是一样的,那你新加的按钮大了点或者小了一点,那就要注意了。
c.操作习惯是否一致。比如,控件内容填充是否联动;鼠标移动到控件上面是否有选中效果;错误信息显示位置等等。
d.编码风格,注释书写格式是否一致等等。
再举一个我在实际工作上遇到的一个例子。一个新手实现完成了一个用户登录界面及其中的逻辑。但是如果由于忘记密码或者其它原因导致无法登录成功时,登录界面无法关闭,用户无法完成其它操作,比如退出软件。这是一个真实案例,而且在我提出不完善的地方后,那个新人还拒绝修改。我最后是用一起玩过的暗黑登录战网的例子才让他明白功能上的缺陷。主体上登录功能是实现的,也测试通过了,这个没有问题。但是遗留一个无法退出登录操作问题,这就是功能实现不到位,或者说不完整。在真实开发中,这和没有完成功能基本上是等价的。
另外一个造成实现功能不到位的原因是缺乏质量意识和产品意识。这个错误我是犯过的,在一个外包公司工作时就有这个问题。那是第一次开发有用户可以直接操作的界面的软件产品。之前的想法总是关注在功能是否实现上,而忽略了上面提到的那些注意点,所以我开发的模块没有通过测试。注意,当时我是有六年开发经验,还有高程证书的,绝对不是一个新手。这里强调一下,希望新手注意。
三.程序调试不通
从整体上来说,调通程序对新手是一个坎。能够顺利的调通程序是开发者可以独立工作的一个重要考察标志。回想自己学习C语言的时候,还是吃了点苦头才走过来的。先谈一下编译错误,主要是两个:语法错误和链接错误。
对于编译中的语法错误,可以选择出现在前面的错误先解决。这是因为编译器在检查代码的语法错误时,有可能前面的语法错误导致后面的错误。这时先尝试解决后面的语法错误,就不太明智,难度相对就会大点。所以建议解决语法错误时从第一个或者靠前面的开始。另一个策略是先解决容易的语法错误。有时候出现语法错误很多,看着是比较烦的,这时可以先把简单的明显的错误解决掉。比如,看到书上的一段代码,想试一下,于是将代码敲进去然后编译。这时出现语法错误就比较多了,那么可以将类似标识符未定义(产生这个错误的原因可能是敲代码时的手误)之类的错误先解决掉。这样就可以逐步减少错误的数量,从而可以让我们的注意力能更好的集中在难度大的错误上。从总体上来说,语法错误的排查解决应该算是一个比较容易处理的问题。以当前流行的开发工具来说,对语法错误的排查提供了越来越好的支持,所以即便是新手我还是建议能够靠自己的能力完全排查掉语法错误。
相对于语法错的简单而言,排查编译时的链接错误的难度就会大一点了。在C或者C++这类编译器中,没有将必要的库文件引用到项目中是产生链接错误的一个频率很高的原因。当出现链接错误时,可以先肯定一点,源代码本身已经没有问题了。因为代码只有在通过词法和语法检查才会编译生成代码,所以产生链接错误时,代码本身的出问题的可能性就不大了。这个线索可以告诉我们解决问题方向就不要在源代码本身去找了。需要注意一点,对于编译时的错误,一定要看清错误的信息,明确错误信息说的是什么然后再去排查错误,切记。因为很多的情况下错误信息就直接给出了答案的。举一个我在开发EntityModelStudio时自己遇到的问题。我在一个工程里引用了另一个工程的dll,但是由于操作问题,实际引用的dll是另一个位置的不同版本的同名dll文件。产生的后果是dll工程调试什么问题都没有,但是引用后调试时怎么做都是错的。新手可以体会一下实际问题是不太按规矩出牌的。
除了编译错误,另外一类就是所谓的逻辑错误了。这才是开发者调试程序的重点。实际上在很多场合两,三个小时排除几百,上千的语法错误都不算什么,所以排除实现不了指定功能的逻辑错误才是核心问题。逻辑错误的具体表现是程序执行的结果达不到预期的结果,或者只是部分达到要求而不是完全符合要求。造成这个结果的原因当然是各种各样的,可能是选择算法有问题,调用的方法不正确,等等。对于这个问题我给出的建议是请掌握调试程序的基本技巧,就是断点设置和变量值的查看。
断点的设置可以从发现出错位置的方法的开始处设置,或者某一个肯定在出错位置前面的地方,当程序停在断点后按F10(Step Over的意思)逐步跟踪代码。当发现执行某一行代码执行完时结果不对,那么就要查看这行代码了。如果是单一的代码那么根据错误的结果修改代码,如果是方法调用,那么就要进入到这个方法的内部(按F11,Step Into意思),使用相同的办法继续跟踪。用这个办法可以逐步逼近错误的位置。有时为了能提高跟踪代码的效率,在自己对错误和代码比较熟悉的情况下,可以在某些关键点设置断点,查看程序的执行情况。这个事情听上去比较简单,但是遇到一些新手却会犯这么一个错误。F10和F11的功能是清楚的,但是在看到方法调用后结果不对时,却不知道要进入该方法的内部继续跟踪代码。这里提醒新手,F10和F11就是这么交替使用的,直至定位到最终出错的位置。如果仅仅使用这样的方法就可以定位并排除错误,那么这样的错误还是比较容易的,有可能是最容易的情况。下面介绍稍微有一点难度的情况。
有时候我们可能需要写一点代码(这些代码还不能算是测试代码),为使用断点设置这个调试手段起到辅助的作用。比如,错误的位置是在一个循环体内。如果我们直接简单的在循环体内设置断点,那么循环执行的第一次就停在断点处,然后开始单步跟踪。这时就会有一个问题,这个循环要跟踪多少次,才能等到出现错误的那次循环呢?如果是第一次,或者前几次那还好,如果是100次,200次或者更多次那就麻烦了。对于这样的场合就需要写点代码来辅助调试了。假设循环是for循环,那么在循环体内的恰当位置写下类似下面的代码:
for (int i = 0; ...; i++)
{
// 这些是需要调试的代码,已经存在的,假设有若干行
...
// 这个if语句是需要加入的调试代码
if (i == 100)
{
int a = 10; // 断点设置在这行代码上
}
// 这些是需要调试的代码,已经存在的,假设有若干行
...
}
调试代码起到的效果是,在循环到第101次时程序就会停在断点处了。这可以显著提高调试的效率。还有一些场合我们调试的程序和Windows的消息有关,这个时候断点的设置位置和时机就会比较麻烦。比如,当需要把断点设置在鼠标事件中时或者OnPaint事件时,就会这样。因为直接设置的话,那么每次鼠标事件或者OnPaint事件触发时都会导致程序停下来。而这时还远远没到错误出现的时候。这时可以考虑先不设置断点,等到最后一步操作前再设置断点。比如,需要将用户输入的数据在OnPaint事件中显示在客户区。那么可以考虑在恰当的位置执行一行刷新客户区的代码(对于C#的窗体是Invalidate,对于MFC还可以考虑发消息),断点先设置在那行刷新的代码上。等程序执行停在那行代码时,再在OnPaint方法中设置断点,然后按F5,这样就可以让OnPaint中断点直接停在我们需要的时刻上。再给出一个可供选择的方案是,如果可能,将OnPaint中的代码拿出来,放到按钮的点击事件中,这样调试就可以避开原来的麻烦了。更一般的思路是,在程序中加入调试代码,一般是一个if语句。该语句的条件表示了你希望程序停下来单步跟踪的时刻,然后将断点设置在这个if语句内部的代码上就可以了。
有些场合可以考虑使用控制台输出信息的办法。当然也可以选择写文件,作用是一样的,但是对于Visual Studio开发环境来说控制台输出对调试程序更为方便些。在程序的特定位置写入一些输出信息到控制台的代码,C#中我用Console类的WriteLine方法,或者Debug类的同名方法。这样程序的执行不会被打断,同时又能看到必要的信息。这是一个很不错的优点。在调试的时候,如果觉得有困难,错误位置无法确定,那么我建议可以采取逐步解决的办法。先实现最简单的情况,然后调试通过,接着再实现下一个情况,然后再调试通过。如果可能这里我想强调一下,这里分步依次实现的情况最好能够做到独立。比如,代码可以用明显的if else语句或者switch语句的分支隔开,或者代码放在不同的方法中。这样在调试程序时可以让我们每次只关注在一个情况上,并且处理不同情况的代码至少在视觉上没有相互干扰,这对我们解决问题是有帮助的。这个方法在很多场合是很有效的,用好的话还是简化问题的方法。在开发EntityModelStudio的时序图时我采用的就是这个方法,所不同的是时序图的各种操作行为的分类计数对新手来说是一个有点难度的问题。
四.两个有用的技巧
再介绍两个个人认为很有用的技巧,那就是对比法和关键点查找,这是我在工作第一年维修家电时积累的经验,实践发现在软件开发中也是有用的。所谓对比法就是手上有一份代码(或者例子)可以实现要求功能,而我们现在需要实现相同的功能,那么我们就可以对照着例子改,直至实现需要的功能。听上去很简单,事实上这个方法的表述也确实很简单,也许会有人觉得这和google或者百度后的copy/paste有区别么?应该说都会用到copy/paste,这点相同,但是思路上不同,这是区别。我用前几天在CSDN上看到的一个帖子作为例子来说明。
帖子中的有如下的代码(不是原文,但是意思相同):
public class MyClass
{
int Age {get; set;}
}
帖子的问题是:给MyClass对象的Age属性赋值,提示出错。这当然是一个很新手的初级问题,直接加上public修饰符就可以解决问题。下面试一下用对比法来解决。
问题的现象是不能通过对象访问属性,而事实是应该可以访问,那么就要试图去找一个例子,而那个例子是可以通过对象访问属性的。这样的例子上网很容易找,然后对比差异,应该很快发现差一个public,加上就可以了。这个和copy/paste还是接近的。但是有的时候找不到这个例子怎么办?那么我们可以通过对比别的内容来尝试解决问题。比如,现在问题是通过对象访问不了属性,那么可以通过对象访问方法么?如果找到通过对象可以访问方法的例子,那么就可以考虑通过调用方法的例子来修改属性。这个就是思维技巧的差异了,要灵活应用方法。在加大一点难度,那么上不了网怎么办?这时可以考虑通过现有工程中已有的代码作为例子。这个问题的难度应该说不大,但是思考的步骤对于新手来说应该是有点挑战的。从解决问题的思考技巧来说,如果一个新手自己就可以有这样的思路,那我就认为这个新手是有才的。希望新手可以体会一下。不同的技巧使用对比法可以在更为复杂的情况下解决难得多的问题,这部分在下篇讨论了。
从我个人的经验来说,新手在学习开发或者一个开发者开始一个新的开发方向的时候,典型的就是使用一门新的语言,会遇到一些无法用常理可以解释的问题。比如我自己的第一个Windows程序。我用了很短时间将30行左右的代码敲入计算机,但是却用了几乎整整八天的时间才调试通过。这个Hello World级别的程序,最终查出的原因是工程名不对。我用的是abc,改成aaa就可以了。这类问题在此后的开发经历中也遇到过,但是总体是越来越少,解决问题所耗费的时间越来越短。但是其共同的特征是无法用常理解释或者莫名其妙的自己消失了。遇到这类问题时,首先确保当前的工程足够简单。如果没有足够简单的工程,可以考虑新建一个。然后可以尝试逐步注释代码找出问题原因。或者先构造一个足够简单并可以通过调试的程序,然后一步一步的修改朝目标逼近。在这一过程中哪一步修改出了问题,那么问题就在那一步上。当然最好每逼近一步就做一次备份。总体上来说这个方法也是属于对比法的范畴。
所谓关键点查找是指程序执行在时间上是顺序的,由此总体上实际的代码也是顺序执行的。那么一旦程序出现问题,我们就可以把程序在出问题的点上分成两部分,出问题之前的和出问题之后的。如果我们看到的结果是正确的,那么问题点应该在当前时间点后执行的代码中,注意我说的是时间点的先后,不是源代码物理位置上的先后。所以我们应该在那些代码中去查找,并在恰当的位置设置断点。前面提到的F10和F11也是这个意思的具体表现。这里再次强调这个思路的意思是,希望新手在遇到问题而困惑时这个思考的技巧可以帮助自己理顺思路,而不是仅仅把F10和F11的作用看成是两个按键对应的功能。
四.给新手的建议
好了,这篇博文大致就写到这里,感觉谈的内容对新手来说可能有点多了。这些方法和技巧在使用中我相信会对新手是有帮助的。但是任何方法的应用都需要一个逐步熟练的过程,就像人的成长一样。所以新手也不能抱着一口吃成一个胖子的心理。务实的做好自己的工作,在实践中逐步提高和进步才是正确的作法。另外也要对自己有信心。其实新手还是有不少优势的,比如学习速度快,适应能力强,什么事情都有热情去做或者愿意做,面对困难有冲击力,这些都是长处。所以面对经验缺乏,能力相对较差的(较差这个词可能用得不好)情况用不着着急或者影响心情。总之尽力而为了就可以了,今天比昨天好,这次比上次好就行了。
五.提问解答
最后我解释一下学习篇中被提问到的一个问题。学习篇中我提到读一本书最好是用20到40分钟能过一遍。有人提问做起来有困难,那么在此解释一下。首先我知道的快速阅读技巧有两个。第一个是正统的快速阅读法,据说斯大林用这个方法可以在四个小时内看完一本五百页的书。通常的阅读方法是用视力最清楚的那个点一次看一个字,快速阅读方式是要求读者用眼睛一次看一段字,比如:三个,七个之类的。这样就会比通常的阅读方法快三倍或者七倍。这个方法是需要练习才能掌握的。第二个方法实际上是一个技巧。那就是只阅读每一段的第一句话和最后一句话,这个方法据说可以用10%的时间获得50%的信息量。这个技巧我没有试过,效果如何就不清楚了。其次,在读计算机书时,可以用视线扫描文字,这个不是快速阅读,这比快速阅读还要快很多。然后用视线滤出敏感的词汇,发现是重点的,感兴趣的或者不懂的就停下来看一下。另外已有的开发经验也至关重要。比如,阅读ADO开发数据库的书,我就直接用视线扫面书上的例子代码,ADO的初始化也看了一下,然后关注使用的步骤和其中的方法名。其原因是在这之前我有用C++使用ODBC的开发经验。另外需要注意这个方法对新手可能不太适用,因为新手经验少,调试程序的能力有限。新手看懂代码不等于能调通程序,所以新手一定要上机敲代码试一下才算会。但是老手就不用了,足够的开发经验可以保证这类入门级的例子,看懂就能调通。所以开发经验在读书时也是有用的。另外一个需要澄清的问题是本系列的文字与任何培训机构没有任何关系。如果还有别的问题或者愿意交流的可以加入我的群:231233168。
下一篇还是讨论解决问题的方面的内容,但是难度会增加很多,题目应该是问题解决篇(下)。这是针对有一定开发经验的开发者的。