zoukankan      html  css  js  c++  java
  • Try your best to make your code elegant!

     

    浅淡“人如其‘码’”

    ——看一道C基础笔试题有感

    又是好久没有写博客,年底比较忙,《Performanced C++》系列也在努力酝酿但没有更新。不过近日出笔试题时,看到个题,以及各种不正确答案和烂代码,感触实在太多。

    忘了以前在哪看过,说笔试时候写的代码虽然只有短短几行至几十行,但却能完完全全把一个coder的真实水平体现的淋漓尽致,正所谓古有“人如其文”,看他写的文章就知道他有多少斤两。对于我们coder而言,就是“人如其‘码’“,一道看似简单的笔试题,其实完全可以考察出coder至少以下n个方面:

    1、代码风格和规范:从落笔第一刻开始体现

    2、异常安全的“危机感”:从是否对传入参数的有效性有“敏感”开始,记住一条准则:永远不要信任用户的输入,这也是防SQL注入等手段的最基本原则之一

    3、类型安全的“危机感“:很简单的其中一个方面,是否考虑过,诸如指针转换是否安全这样的问题

    4、思路清晰程度:很简单, 从写下的代码是否涂了又改,改了又涂的多少程度就可以看出来。但完全没有任何涂改的答案,通常可能知道这个题的答案背下来的,那么,面试时稍加深入一问,就不攻自破

    5、算法和数学水平:抽象的讲即“解决问题的能力”,就是最终是否能给出一个正确解,不管代码是否烂、有无考虑其它方面的问题

    6、性能优化思想:是否有性能优化、考虑给出最优解的思想,即使最后的答案不是最优的,但在能保证正确性的基础上尝试过努力

    7、对所使用的语言的理解程度:这条可能很多人忽视了。对于C语言的笔试,这一条旨在要求笔试者,尽可能从C语言的思考方式——底层,来解决问题,通常就是操作指针,而不是使用库函数甚至第三方“高级”库,假设是Java笔试,是否有OOP的设计思想。当然,明显可以看出,使用C语言笔试更有区分度。

    8、平时所写或所读的代码量:通常可以有个估计,起码可以判断笔试者是否有读过某些名著,因为读过就可以把此题至少解的半斤八两

    9、移植性如何:很简单,通常对于字符串操作的函数,如果想要声明int是使用的是size_t,证明你考虑过这方面的问题,起码看过一些源代码

    ……想起来再补充

    好了,上面说了很多废话,下面看这篇文章的主要内容:

    笔试题,C语言,实现strstr函数。这是一道基础得不能再基础的C语言笔试题,但我google了半天,发现能给出真正正确答案的,却不多。由此可见在真正笔试中,如此一道基础题目,如果严格按照上述几条来判定,足以刷掉80%以上的笔试者。 

    假设我现在是一个笔试者,拿到此题,假设我不知道strstr是干什么用的(其实题目会告诉你,要求你实现的这个函数的作用。但是,由于strstr是标准C库函数且有点儿常用,而你不知道他干什么用,那么基本可以假定你没有用过这个函数或是用过了也不熟悉,更不会清楚其内部实现,那么也就基本可以假定,你对标准C库不熟悉,也不会清楚其内部实现,那么也就基本可以再假定,你对C语言并不熟悉,从而假定你对以下东西都不怎么熟悉:Unix/Linux、GCC/GDB……从而基本假定这个C语言的职位并不合适你),那么最好的方法就是暂时不去参加笔试,特别是C语言笔试,详细阅读《The C Programming Language K&R》、《C A Reference Manual(5th Ed)》、《UNIX环境高级编程》等世界名著,并自己在Linux下把上述书中提到的标准C库函数都coding一遍,再上战场。

    现在假设我知道strstr的作用,很简单,不就在s(src)源字符串中查找目标字符串t(target)吗?找到返回第一次匹配源串中的开始指针,没找到返回NULL。

    下面是一个正确的实现: 

    复制代码
     1 const char* mystrstr(const char* s, const char* t)
     2 {
     3     const char *p = s; const char *q = t;
     4     if(NULL == s || NULL == t)
     5         return NULL;
     6     while(*s)
     7     {
     8         p=s;q=t;
     9         while(*p == *q && *q != '\0')
    10         {
    11             p++;
    12             q++;
    13         }
    14         if(*q == '\0')
    15             return s;
    16         s++;
    17     }
    18     return NULL;
    19 }
    复制代码

     也许乍看并不觉得这段代码有什么了不起,但如果你和以下的烂代码比较(甚至都不正确),就会发现上面这段代码是多么的elegant(优雅)。

    另外要提一句,Linux Kernel/Gcc并非如此实现这个函数,因为这个函数的算法和性能都不是最优的,在上述几个方面的6并不能得到高分。但如果在笔试中能写出这样的答案,通常已经可以得到满分了。Why?因为上述这段代码,首先一点:自己完全从底层(还不够底层?那只能嵌入汇编了)实现这个函数,而没有调用其它任何库函数。我想这是C语言笔试最基本的常识之一。说到这里又要吐槽了,我年轻时候参加一加做金融类软件的公司的笔试,C++的,其中有一道题,把一个Unix时间戳(1970年开始的秒数)转换为当前时间。OK,小case,我写了半天,自己经过计算完成了这个函数。结果面试时,面试官一看就愣了,问我,这个函数要这样实现吗?我一听以为自己写的有问题,没想到面试官说:“你不会用时间函数吗? 不会用时间函数库吗?不管哪个语言都有时间函数吧?!需要自己来实现吗?!你自己写,可能写对吗?!这个时间计算很复杂的,你怎么可能那么快就写出来了?!”于是接下来,愣的就是我了,我当时想问一句“你需要写代码吗?不会买产品外包吗?不管什么需求都可以买到产品或外包吧?!需要自己来开发吗?!……”不过我还是忍住了,毕竟笔试者是弱势群体。有趣的是,这位奇葩的面试官还通过了我的笔试,让我过几天再去复试,我没敢去。我也不知道这样的人怎么能成为该公司的技术管理人员的。我只知道,我回家后,把当天写的时间转换函数又写了一遍,在Linux上跑了几个小时没发现计算有错误。吐槽了那么多,想说的无非就是,既然选择做技术,就要尽可能专业,因为这个行业,是“科学”,实在不是“差不多”、“马马虎虎”、“年底写个报告”、“忽悠忽悠“就OK的行业,如果不能适应这样苛刻的规则,转行是最好的出路。 

    偏题了。接着说上面的笔试题。上面给出的答案,算法并不是最优的,但其它方面做的很好,而且最容易理解。它是O(n2的时间复杂度,熟悉算法的同学,当然知道KMP或其它算法,可以将它优化至O(N)。当然,因为此题考察的并非是KMP算法,而是上述几个方面中除算法优化的其它所有方面。

    下面开始看烂代码。

    烂代码一:看似OK,其实根本就不正确的代码(出处我就不转了),你能发现哪有问题吗?

    复制代码
     1 //烂代码1
     2 char* _strstr(char* s1, char* s2)
     3 {
     4     char * p, *r;
     5     p=s1;
     6     r=s2;
     7     assert(s2 && s1);
     8     while(*r!='\0')
     9     {
    10         while(*p++==*r++);
    11         if(*p=='\0')
    12             return s2;
    13         else
    14         {
    15             p=s1;
    16             r=++s2;
    17         }
    18     }
    19     return NULL;
    复制代码

    考虑最简单的一个test case:s2为"abcdef",s1为“deg",上述代码竟然输出s2中'd'的地址!,就是认为在s2中可以找到"deg“!

    另外,写这个代码的同学,把s2和s1的参数逻辑顺序搞反了,也就是说,标准C库函数strstr的第一个参数,应是源串,第二个参数是目的串(就是要在源串中查找的串)。这位同学刚好到了过来,不仅代码阅读别扭,而且这种与标准C库相背的行为,也是非常不提倡的,同时,代码中的assert判断虽然体现了异常处理思想,但手法十分业余。此答案得分为0,因为连起码的正确性都不能保证。

    烂代码二:改进了一下,但还是不对,你能看出哪又有问题吗? 

    复制代码
     1 char* _strstr(char* s1, char* s2)
     2 {
     3     char * p, *r;
     4     p=s1;
     5     r=s2;
     6     if(NULL == s1 || NULL == s2)
     7         return NULL;
     8     while(*p!='\0')
     9     {
    10         while(*p == *r)
    11         {
    12             p++;
    13             r++;
    14         }
    15         if(*p=='\0')
    16             return s1;
    17         else
    18         {
    19             p=s2;
    20             r=++s1;
    21         }
    22     }
    23     return NULL;
    24 }
    复制代码

     再考虑一个最简单的test case:s1,s2均为"abcdef",上述代码竟然可能输出NULL!认为没有匹配到目标字符串。得0分。

    烂代码三:可以正确工作的代码,但风格实在太差,又是for,又是临时变量,两次和'\0'的比较竟然风格不一致……这位同学,可以将你的代码写的elegant一点吗?

    复制代码
     1 char* my_strstr( char* str1, char* str2 )
     2 {
     3   if (NULL == str1 || NULL == str2)
     4   {
     5        throw;
     6  }
     7  
     8  char *p = NULL;
     9  char *q = NULL;
    10  const char v = '\0';
    11  for (int i=0; v != str1[i]; ++i)
    12  {
    13    p = &str1[i];
    14    q = str2;
    15    
    16    while (v != *q && *q == *p)
    17    {
    18       ++p;
    19       ++q;
    20   }
    21   
    22   if ('\0' == *q)
    23   {
    24         return &str1[i];
    25   }
    26  }
    27  
    28  return NULL; 
    复制代码

    另外,既然是C代码,throw从何而来?这就是用C++来写C的例证,通常就是对所使用的语言没有较深入的理解,而是只知皮毛,通常也是各种低效垃圾代码产生的前罩。得分5(满分10)。

    现在你知道了,这么基础的一道题,为什么那么多人写不对?并不是说这些笔试者水平就一定差,但通常就是细心和认真的程度不够,即使是在自己的笔试中。 

    更多烂代码就不再举例了,写这篇文章的目的,就是希望自己和众园友能引以为戒,Try your best to make your code elegant! 

    iCC Develop Center
     
  • 相关阅读:
    怎么快速掌握一门新技术
    Linq相关
    C# 参数按照ASCII码从小到大排序(字典序)
    测试工具
    sql 创建临时表
    sql行合并
    WCF相关
    免费开源分布式系统日志收集框架 Exceptionless
    VPS,虚拟主机,云主机,独立服务器区别
    c# Dictionary的遍历和排序
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2872238.html
Copyright © 2011-2022 走看看