能编程与会编程

WinPcap编程(二),winpcap编程

0.

这一次具体讲抓包的两种方法。

(建议)清除ARP表,最好自己写个批处理命令。快一点。

 

1.0 抓包步骤

步骤很简单:先打开适配器列表 –> 选择适配器 –>
通过遍历链表的方式到达你选择的适配器位置 –> 打开设备 –>
开始抓包。

每一个步骤都是一个函数。了解步骤,后面就好办。

首先,了解一个数据类型pcap/pcap_t。它代表一个打开的设备,理解成它就是适配器就行(实际上是这个适配器的描述符)。这个结构体对用户来说是不透明的,它通过wpcap.dll提供的函数,维护了它的内容。

 

然后,跳转至你的设备在WinPcap编程(一)中存在,就不多说。

之后,打开设备的函数:

奥门泥斯人 1pcap_t*
pcap_open ( const char * source, int snaplen, int flags, int
read_timeout, struct pcap_rmtauth * auth, char * errbuf ) View Code

备注:

奥门威尼斯人吴乐城,  第一个参数:source是我们所要打开的设备。当我们获取所有的设备之后,这个source就是d->name。不能为NULL。

  第二个参数:snaplen是我们抓取的数据包的大小,100就是抓取整个数据包的前100B,65536最大,把整个包都包括了。

  第三个参数:flags设置为混杂模式(PCAP_OPENFLAG_PROMISCUOUS ),即不管数据包是否给我,我都去捕获。这样可以捕捉局域网内所有的包。

  第四个参数: read_timeout设置超时时间,以毫秒计(1s=1000ms)。在适配器上进行读取数据操作的时候,不管网络上有没有包,都会在 read_timeout这个时间内响应。

                设置为0意味着没有超时。没有数据包到达,读操作将永远不会返回。

                设置成-1,无论有没有数据包到达,读操作都会立即返回。

  第五个参数:auth远程机登录信息,若本地则为NULL;

  第六个参数:errbuf 出错信息。

 

1.1抓包的两种方式:

  1.1.1 回调函数的方法进行抓包

  每次抓到包就用回调函数进行处理。处理完接着抓包(如果设置了num的话)。

  抓包函数:

奥门泥斯人 2int
pcap_loop ( pcap_t * p, int cnt, pcap_handler callback, u_char *
user ) View Code

备注0:  

  第一个参数:P即是打开的设备。

  第二个参数:cnt表示捕捉个数。

  第三个参数:回调函数指针。对捕捉的数据包进行操作 。

  第四个参数:用户信息,一般为NULL。

函数返回值:

  返回值为0,捕捉了cnt次,正常返回。

  返回值为-1,发生错误。

  返回值为-2,使用pcap_breakloop()结束循环。

 

 

  回调函数抓包还有一种方法, pcap_dispatch() 。

  区别是: pcap_ dispatch() 当超时时间到了(timeout expires)就返回 (尽管不能保证) ,而 pcap_loop() 不会因此而返回,只有当 cnt 数据包被捕获。

      所以,pcap_loop()会在一小段时间内,阻塞网络的利用。pcap_dispatch() 函数一般用于比较复杂的程序中。

 

 

 

  1.1.2 不利用回调函数

  回调函数比较好,但是有时候需要不用回调函数的方法,特别在多线程里面。

  我用C# +
WPF写的,用回调函数的话一直出现错误。仔细考虑,应该是托管与非托管的问题。看了SharpPcap的源码,貌似也没有好的解决方案。

  【有好的方案的看见了希望能够提供一下,感谢。】

  

  不利用回调函数抓包的话,方法比较简单,每次只能抓一个包,加个while()循环就OK了。

  

奥门泥斯人 3int
pcap_next_ex ( pcap_t * p, struct pcap_pkthdr ** pkt_header,
const u_char ** pkt_data ) View
Code

 

备注: 

  第一个参数:打开的设备。

  第二个参数:WinPcap添加的一些信息。(时间戳,捕捉到包的长度,实际包的长度)。

  第三个参数:实际数据包。

 

函数返回值:

  返回值为1,正常返回;

  返回值为0,超时后返回一个无效的包;

  返回值为-1,发生错误;

  返回值为-2,读到EOF。

 

源码:

奥门泥斯人 4#define
WIN32 #include “pcap.h” #include “winsock.h” #include “time.h” /*
packet handler 函数原型 */ void packet_handler( u_char *奥门泥斯人,param, const
struct pcap_pkthdr *header, const u_char *pkt_data ); int main() {
pcap_if_t *alldevs; pcap_if_t *d; int inum; int i = 0; pcap_t
*adhandle; char errbuf[PCAP_ERRBUF_SIZE]; /* 获取本机设备列表 */
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf)
== -1) { fprintf(stderr, “Error in pcap_findalldevs: %s\n”, errbuf);
exit(1); } /* 打印列表 */ for (d = alldevs; d; d = d->next) {
printf(“%d. %s”, ++i, d->name); if (d->description) printf(”
(%s)\n”, d->description); else printf(” (No description
available)\n”); } if (i == 0) { printf(“\nNo interfaces found! Make
sure WinPcap is installed.\n”); return -1; } printf(“Enter the
interface number (1-%d):”, i); scanf_s(“%d”, &inum); if (inum < 1 ||
inum > i) { printf(“\nInterface number out of range.\n”); /*
释放设备列表 */ pcap_freealldevs(alldevs); return -1; } /*
跳转到选中的适配器 */ for (d = alldevs, i = 0; i< inum – 1; d =
d->next, i++); /* 打开设备 */ if ((adhandle =
pcap_open(d->name, // 设备名 65536, //
65535保证能捕获到不同数据链路层上的每个数据包的全部内容
PCAP_OPENFLAG_PROMISCUOUS, // 混杂模式 0, // 读取超时时间 NULL, //
远程机器验证 errbuf // 错误缓冲池 )) == NULL) { fprintf(stderr,
“\nUnable to open the adapter. %s is not supported by WinPcap\n”,
d->name); /* 释放设备列表 */ pcap_freealldevs(alldevs); return -1;
} printf(“\nlistening on %s…\n”, d->description); /*
释放设备列表 */ pcap_freealldevs(alldevs); /* 开始捕获 */
pcap_loop(adhandle, 30, packet_handler, NULL); system(“pause”); return
0; } /* 每次捕获到数据包时,libpcap都会自动调用这个回调函数 */ void
packet_handler(u_char *param, const struct pcap_pkthdr *header,
const u_char *pkt_data) { struct tm ltime = {0}; char timestr[16];
time_t local_tv_sec; /* 将时间戳转换成可识别的格式 */
local_tv_sec = header->ts.tv_sec; localtime_s(&ltime,
&local_tv_sec); strftime(timestr, sizeof timestr, “%H:%M:%S”, &ltime);
printf(“%s,%.6d len:%d\n”, timestr, header->ts.tv_usec,
header->len); } 回调函数
奥门泥斯人 5#define
WIN32 #include “pcap.h” int main() { pcap_if_t *alldevs; pcap_if_t
*d; int inum; int i = 0; pcap_t *adhandle; int res; char
errbuf[PCAP_ERRBUF_SIZE]; struct tm ltime; char timestr[16];
struct pcap_pkthdr *header; const u_char *pkt_data; time_t
local_tv_sec; int j = 10;//限制只抓10个包 /* 获取本机设备列表 */ if
(pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) ==
-1) { fprintf(stderr, “Error in pcap_findalldevs: %s\n”, errbuf);
exit(1); } /* 打印列表 */ for (d = alldevs; d; d = d->next) {
printf(“%d. %s”, ++i, d->name); if (d->description) printf(”
(%s)\n”, d->description); else printf(” (No description
available)\n”); } if (i == 0) { printf(“\nNo interfaces found! Make
sure WinPcap is installed.\n”); return -1; } printf(“Enter the
interface number (1-%d):”, i); scanf_s(“%d”, &inum); if (inum < 1 ||
inum > i) { printf(“\nInterface number out of range.\n”); /*
释放设备列表 */ pcap_freealldevs(alldevs); return -1; } /*
跳转到已选中的适配器 */ for (d = alldevs, i = 0; i< inum – 1; d =
d->next, i++); /* 打开设备 */ if ((adhandle =
pcap_open(d->name, // 设备名 65536, // 要捕捉的数据包的部分 //
65535保证能捕获到不同数据链路层上的每个数据包的全部内容
PCAP_OPENFLAG_PROMISCUOUS, // 混杂模式 1000, // 读取超时时间 NULL, //
远程机器验证 errbuf // 错误缓冲池 )) == NULL) { fprintf(stderr,
“\nUnable to open the adapter. %s is not supported by WinPcap\n”,
d->name); /* 释放设列表 */ pcap_freealldevs(alldevs); return -1; }
printf(“\nlistening on %s…\n”, d->description); /* 释放设备列表
*/ pcap_freealldevs(alldevs); /* 获取数据包 */ while ((res =
pcap_next_ex(adhandle, &header, &pkt_data)) >= 0&&j>0){ if (res
== 0) /* 超时时间到 */ continue; /* 将时间戳转换成可识别的格式 */
local_tv_sec = header->ts.tv_sec;
localtime_s(&ltime,&local_tv_sec); strftime(timestr, sizeof timestr,
“%H:%M:%S”, &ltime); j–; printf(“%s,%.6d len:%d\n”, timestr,
header->ts.tv_usec, header->len); } if (res == -1){ printf(“Error
reading the packets: %s\n”, pcap_geterr(adhandle)); return -1; }
system(“pause”); return 0; } 非回调函数

 

0.
这一次具体讲抓包的两种方法。
(建议)清除ARP表,最好自己写个批处理命令。快一点。 1.0 抓包步骤
步骤…

不得不说的异步编程,不得不说异步编程

1、什么是异步编程?

   
异步编程就是把耗时的操作放进一个单独的线程中进行处理(该线程需要将执行进度反映到界面上)。由于耗时操作是在另外一个线程中被执行的,所以它不会堵塞主线程。主线程开启这些单独的线程后,还可以继续执行其他操作(例如窗体绘制等)。

   
异步编程可以提高用户体验,避免在进行耗时操作时让用户看到程序“卡死”的现象。

 

2、异步编程模型(APM)

    APM是Asynchronous Programming
Mode的缩写,即异步编程模型的意思,它允许程序用更少的线程去执行更多的操作。在.NET
Framework中,要分辨某个类是否实现了异步编程模型,主要就是看该类是否实现了返回类型为IAsyncResult接口的BeginXXX方法和EndXXX方法。

   
由于委托类型定义了BeginInvoke和EndInvoke方法,所以委托类型都实现了异步编程模型。

    2.1 Beginxxx方法–开始执行异步操作

         
在需要获取文件中的内容时,我们通常会使用FileStream的同步方法Read进行读取,该同步方法的定义为:

          public override int Read(byte[] array,int offset,int count)

         
当使用上面的方法读取大文件的内容时,会出现堵塞UI线程,导致在文件内容没有读取完成之前,用户不能对窗体进行任何操作(包括关闭应用程序),这时窗体就会出现无法响应的情况。

          为了解决这个问题,微软早在.NET
1.0的时候就提出了异步编程模型,并为FileStream类提供了异步模式的方法实现,即BeginRead方法。该方法会异步地执行读取操作,并返回实现了IAsyncResult接口的对象(该对象存储这异步操作的信息)。

         
下面给出了BeginRead方法的定义,我们可以从中找出它与同步方法Read的区别:

          public override IAsyncResult BeginRead(byte[] array,int
offset,int numBytes,AsyncCallback userCallback,Object stateObject)

         
从以上的异步方法的定义可以看出,该异步方法的前面3个参数与同步方法Read一致,后两个参数userCallback和StateObject则是同步方法所不具备的。userCallback表示异步操作完成后需要回调的方法,该方法必须匹配AsyncCallback委托类型;stateObject则代表传递给回调方法的对象,在回调方法中,可以通过查询IAsyncResult接口的AsyncState属性来读取该对象。该异步方法之所以不会堵塞UI线程,是因为它在被调用后,会立即把控制权交还给调用线程(如果是UI线程调用了该方法,则就将控制权返回给UI线程),然后由另一个线程去执行文件读取操作。

 

    2.2 Endxxx方法–结束异步操作

         
每次调用Beginxxx方法后,应用程序还需调用Endxxx方法来获取操作返回的结果。Beginxxx方法所返回的,是实现了IAsyncResult接口的对象,该对象并非相应的同步方法返回的结果。此时还需要调用Endxxx方法来结束异步操作,并向该方法传递Beginxxx所返回的对象。Endxxx方法返回的类型与同步方法相同,如FileStream的EndRead方法会返回一个Int32类型,代表从文件流中实际读取的字节数。

         
Endxxx方法有许多中方式调用,但有一种是最常用的,即使用AsyncCallback委托来指定操作完成时要调用的方法,在回调方法中调用Endxxx方法来获得异步操作返回的结果。

 1 static void Main()
 2 {
 3     SynchronizationContext sc=SynchronizationContext.Current;
 4     AsyncMethodCaller methodCaller=new AsyncMethodCaller(DownLoadFileAsync);
 5     method.BeginInvoke(txtUrl.Text.Trim(),GetResult,null);
 6 }
 7 private static void GetRsult(IAsyncResult result)
 8 {
 9     AsyncMethodCaller caller=(AsyncMethodCaller)((AsyncResult)result).AsyncDelegate;
10     string returnstring=call.EndInvoke(result);
11 }

 

3、异步编程模型(EAP)

   
虽然前面的异步编程可以解决执行耗时操作时界面无法响应的问题,但APM也同样存在这一些明显的问题,如不支持对异步操作的取消以及不能提供下载进度报告等。然而对于桌面应用程序而言,进度报告和取消操作的功能是必不可少的,所以微软在.NET
2.0
发布时又提出了一个新的异步编程模型–基于事件的异步模型,即EAP(Event-based
Asynchronous Pattern)。

   
实现了EAP的类具有一个或多个以Async为后缀的方法,以及对应的Completed事件,并且这些类支持异步方法的取消和进度报告。在.NET类库中,只有部分类实现了EAP,共17个。在这17个类中,开发过程中使用最多的莫过于BackgroundWorker类了。

    经常使用的属性为:

    CancellationPending:用来指示应用程序是否已请求取消后台操作;

    IsBusy:指示异步操作是否正在运行;

    WorkReportProgress:只是BackgrounWorker能否报告进度;

   
WorkerSupportsCancellation:指示BackgroundWoker是否支持异步取消操作;

    经常使用的方法为:

    CancelAsync:请求取消异步操作;

    ReportProgress:用于引发ProgressChanged事件;

    RunWorkAsync:调用后开始执行异步操作;

    经常使用到的3个事件为:

    DoWork:调用RunWokerAsync时触发的事件;

   
ProgressChanged:调用ReportProgress时触发的事件,程序会在该事件中进行进度报告的更新;

    RunWorkerCompleted:当异步操作已完成、被取消或引发异常时被触发。

    这种方法已经很少用到了,所以这里就不详细介绍了。

 

4、TAP又是什么?

    前面介绍了.NET提供的两种异步编程模式,分别为.NET 1.0中的APM和.NET
2.0中的EAP。虽然这两种异步编程模式可以实现多数情况下的异步编程,但是它们在MSDN文档上都被标注为了不推荐使用的实现方式,因为在.NET
4.0中,微软又提供了更简单的异步编程实现方式–TAP,基于任务的异步模式。

   
该模式主要使用System.Threading.Tasks命名空间中的Task<T>类来实现异步编程,所以在采用TAP之前,首先要引入System.Threading.Tasks命名空间。

    基于任务的异步模式(TAP,Task-based Asynchronous
Pattern)只使用一个方法就能表示异步操作的开始和完成,而APM却需要Beginxxx和Endxxx两个方法分别表示开始和结束,EAP则要求具有以Async为后缀的方法和一个或多个事件。在基于任务的异步模式中,只需要一个以TaskAsync为后缀的方法,通过向该方法传入CancellationToken参数,我们就可以很好地完成异步编程了。而且,还可以通过IProgress<T>接口来实现进度报告的功能。总体来说,使用TAP会减少我们的工作量,是代码更加简洁。

1 Task task=new Task(()=>{.......});
2 task.Start();

 

5、让异步编程So easy——C# 5.0中的async和await

    虽然.NET 1.0和.NET 2.0和.NET
4.0都对异步编程做了很好的支持,微软也逐渐地使用异步编程变得简单,但微软觉得现有的工作还不够,它希望使异步编程的开发过程更为简化,所以在.NET
4.5中,微软又提出了async和await两个关键字来支持异步编程。

    这也是目前.NET
Framework中最简单的异步编程实现方式,因为使用这个两个关键字进行异步编程,思考方式和实现同步编程时的完全一样。

   
async和await关键字不会让调用方法运行在新线程中,而是将方法分割成多个片段(片段的界限出现在方法内部使用await关键字的位置处),并使其中一些片段可以异步运行。await关键字处的代码片段是在线程池线程上运行的,而整个方法的调用确实同步的。所以,使用此方式编程不用考虑跨线程访问UI控件的问题,从而大大降低了异步编程的出错率。

1、什么是异步编程?
异步编程就是把耗时的操作放进一个单独的线程中进行处理(该线程需要将…

你会说编程语言吗?Do you speak code?

人和人之间的沟通交流会用到自己地方的语言,譬如英文、中文、日文等。

而编程语言就是和电脑沟通的语言,学会编程语言,就是用电脑听得懂的语言,告诉它帮你做你想要做的事情。

奥门泥斯人 6

异步编程,c#异步编程

几个月前因为一个事情被diao了。起因是临近上线的时候项目后端统一了消息协议(.proto),然后要我前端也支持。我研究了一天,没走通,要么依赖项太多,要么一直报错,而且需要使用的对象兼容性有问题。当时心里有些急,也有几份抵触这种方案,于是在会上说出了我的想法:能不能友好的发发json,兼容性好也不需要什么第三方解析。结果自然是被否决了,理由是大厂出品的,怎么可能不能用呢,用屁股想想就知道?你为啥遇到问题就想着退缩呢。我无语凝噎。重要是给我强调了能编程与会编程是不一样的

一、线程

1.1 主线程

.net使用main()方法做为程序的入口点,当调用该方法,主线程被创建。

1.2 工作者线程

由主线程创建的线程被称为工作者线程,用于去执行某一项具体的任务。

1.3 前台线程

默认情况下Thread.Start()创建的线程都是前台线程,前台线程可以阻止应用程序的终结,即所有的前台线程执行完成之后CLR*才能关闭应用程序。

前台线程属于工作者线程

1.4 后台线程

后台线程不影响应用程序的终结,所有的前台线程执行完成之后,无论后台线程是否执行完成,应用程序都会关闭。

后台线程一般用于处理一些无关紧要的任务(比如邮箱每隔一段时间检查一遍邮箱等)。后台线程属于工作者线程。

奥门泥斯人 7

 1      static void Main(string[] args)
 2         {
 3             Console.WriteLine("主线程开始");
 4             Thread t1 = new Thread(Task1);
 5             t1.Start();
 6 
 7             Thread t2 = new Thread(Task2);
 8             t2.IsBackground = true;  //后台线程
 9             t2.Start();
10         }
11 
12         public static void Task1()
13         {
14             Thread.Sleep(1000);   
15             Console.WriteLine("前台线程开始");
16         }
17 
18         public static void Task2()
19         {
20             Thread.Sleep(3000);
21             Console.WriteLine("后台线程开始");
22         }

View Code

以上代码运行结果如下图所示,由此可以看出前台线程执行完成之后,应用程序关闭。未输出“后台线程开始”,后台线程未执行。

奥门泥斯人 8

奥门泥斯人 9

 1     static void Main(string[] args)
 2         {
 3             Console.WriteLine("主线程开始");
 4             Thread t1 = new Thread(Task1);
 5             t1.Start();
 6 
 7             Thread t2 = new Thread(Task2);
 8             //t2.IsBackground = true;  //后台线程
 9             t2.Start();
10         }
11 
12         public static void Task1()
13         {
14             Thread.Sleep(1000);   
15             Console.WriteLine("前台线程开始");
16         }
17 
18         public static void Task2()
19         {
20             Thread.Sleep(3000);
21             Console.WriteLine("后台线程开始");
22         }

View Code

以上代码运行结果如下图所示,两个线程都为前台线程,两个线程全部执行完成之后,应用程序关闭。

 奥门泥斯人 10

开完会情绪有点低落,回到座位上打开github
继续找方案,不一会儿居然找到了!啪啪打脸的感觉好酸爽。然后开始思索…

二、线程池

线程池是为突然大量爆发的线程设计的,通过有限的几个固定的线程为大量的操作服务,减少了线程创建和销毁的时间,从而提高效率。

ThreadPool使用与并发运行若干个任务且运行时间不长且互不干扰的场景。ThreadPool创建的线程为后台线程。

奥门泥斯人 11

 1 static void Main(string[] args)
 2         {
 3             Console.WriteLine("主线程开始!");
 4             //创建要执行的任务
 5             WaitCallback workItem = state => Console.WriteLine("当前线程Id为:" + Thread.CurrentThread.ManagedThreadId);
 6             //重复调用10次
 7             for (int i = 0; i < 10; i++)
 8             {
 9                 ThreadPool.QueueUserWorkItem(workItem);
10             }
11             Console.ReadLine();
12         }

View Code

以上代码运行结果如下图所示,由此可见并不是每次执行任务都创建一个新的线程,是在循环利用线程池中维护的线程。如果去掉最后一句, Console.ReadLine(),仅输出“主程序开始”就直接退回,可知线程池创建的线程为后台线程。

奥门泥斯人 12

 

 持续维护中…….

 

 

 

 

 

CLR:

线程同任务的区别

async await的使用,什么时候使用,作用是什么

 

一、线程 1.1 主线程
.net使用main()方法做为程序的入口点,当调用该方法,主线程被创建。 1.2
工作者线程 由主线程创…

是否抵触


一件事带有抵触情绪之后,那基本是做不好的。在找方案的时候,眼中只看到“NO”,而自动忽略了“YES”。解决方法在你眼前你可能都看不到。就像一个人不想做事就找借口,这些借口在他自己看来都是很合理的。要带有这种情绪,那不如先别开始,要么自我消化,要么拿更好的方案去说服别人。

只要有一种解释是对自己有利的,我们便不想去推敲和反驳,再漏洞百出的事情看上去也不无可能,而且只要一种解释是可能的,我们就认定是一定的,强大的情绪大脑会阻止理性大脑往深入的想。而对于自己不利的解释,我们或忽略,或者异常仔细的去推敲,抓住一个漏洞则相信自己推翻了所有的解释。—-《暗时间》

所以一件事情在自己着手做的时候,先整理好类似的情绪。很多时候摆在我们面前的方案很多,如果是你熟悉的领域当然好选择,利弊清晰,能快速判断。但不熟悉的时候,就要注意到每个方案所适用的环境和条件,要归类和总结,自己所遇到的问题本身已经提供了一些前提条件。用这些条件先过滤到一部分。而不是瞎猫抓死耗子一样一个个去碰。

另外一点,我们在尝试新的事物的时候,总是会遇到各种困难,不同的人在不同的碰壁次数之后会退出。用程序员的话来说,我们都在for循环,区别在于你是什么情况下break;的。有的人退出阈值高,这是能坚持的一类人,有的人退出阈值低。过早退出的原因在于对未来的不确定性,对投资时间最终无法收到回报的恐惧。其实在坚持坚持,再想一想,答案马上就出来了。

不要等待


编程工作中我们经常遇到需求不完整,接口文档没有,接口返回值错,误诸如此类
别人的因素,然后导致项目进度缓慢或者停滞。其实我想说,编程简直太轻松愉快了,因为还有人给你提供需求,提供设计稿,你实现就行了。实际生活中很多事情根本没有人会告诉你需求,比如自己装修,你需要定风格,然后选材料,按照流程喊施工队伍,考虑风格和成本,考虑材料合不合适,喜不喜欢,甲醛高不高等等。建材市场鱼龙混杂,装修师傅可能阳奉阴违。即使你监工他都敢用料不够,总有一款让你交点学费。
可能很忙的时候,父母又生病了…..
生活的事情更考验的人的协作和沟通能力、承压能力。没有谁告诉你截止日期和责任,但是你没做好,就是没有做好,不要说什么装修的人坑了你。工作的事情责任范围清晰,不属于你的锅你都可以甩的远远的。但如果团队内部都是这样的氛围,那又能成什么事。雪崩的时候,没有一片雪花觉得自己有责任,应该积极主动起来,因为等待耗费的也是自己的时间。

做完了还是做好了


按照需求和效果图,你可能很快就实现了所有功能和效果。但是自己测试了吗?可用性高吗?稳定吗?兼容性如何?功能做完往往只到了一个阶段,后面还需要自己做检验性工作。确保交出去的东西是ok的,达到预期的。有信心对别人说我做好了,让别人用的放心。而不是说一句我做完了,暗藏的坑让别人先踩。有时间还应该思考一下,代码是否可以更简洁?接口设计是否可以更简明?多个类似方案是否可以统一成产品?思考的价值总会为你省下未来的时间,特别是重复劳动的时间、遇到问题沟通的时间。

知识体系完善


你选择的方案,都是你所知道的方案。也就是说,你的知识范围,决定了你的处理能力范围。甚至于,有的知识你会,但关键的时候你未必想的出来。这个过程就像解题,自己苦思冥想不得,看到答案恍然大悟。为什么呢?你的大脑就像一个图书馆,知识的碎片就如书架上的书,你想用的时候,发现找不到地址了。

知识分两种,一种是我们通常所谓的知识,即领域知识。二是关于我们大脑吸收知识的机制的知识,后者不妨成为元知识。虽说这也是领域知识,但跟其他的领域知识不同的是,它指导我们学习其它所有的领域知识。

除了完善我们的领域知识,也许需要补充一下其他领域的,譬如心理学和思维方面的,当然最好是到生活中去解决问题。

小结:会编程是基于现有经验办事的能力,而能编程是对整个事情的解决能力。在学习一门技术的成本差不多的情况下,差别就来自于编程之外的能力。

相关文章