小菜编程成长记(一 面试受挫——代码无错就是好?)

2016-02-19 11:54 4 1 收藏

下面是个小菜编程成长记(一 面试受挫——代码无错就是好?)教程,撑握了其技术要点,学起来就简单多了。赶紧跟着图老师小编一起来看看吧!

【 tulaoshi.com - 编程语言 】

小菜今年计算机专业大四了,学了不少软件开发方面的东西,也学着编了些小程序,踌躇满志,一心要找一个好单位。当投递了无数份简历后,终于收到了一个单位的面试通知,小菜欣喜若狂。
        到了人家单位,前台小姐给了他一份题目,上面写着,“请用C++、Java、C#或VB.NET任意一种面向对象语言实现一个计算器控制台程序,要求输入两个数和运算符号,得到结果。”
        小菜一看,这个还不简单,三下五除二,10分钟不到,小菜写完了,感觉也没错误。交卷后,单位说一周内等通知吧。于是小菜只得耐心等待。可是半个月过去了,什么消息也没有,小菜很纳闷,我的代码实现了呀,为什么不给我机会呢。
        小菜找到工作三年的师哥大鸟,请教原因,大鸟问了题目和了解了小菜代码的细节以后,哈哈大笑,说道:“小菜呀小菜,你上当了,人家单位出题的意思,你完全都没明白,当然不会再联系你了”。
        小菜说:“我的代码有错吗?单位题目不就是要我实现一个计算器的代码吗,我这样写有什么问题。”
代码如下:

class Program
{
    static void Main(string[] args)
    {
        Console.Write("请输入数字A:");
        string A = Console.ReadLine();
        Console.Write("请选择运算符号(+、-、*、/):");
        string B = Console.ReadLine();
        Console.Write("请输入数字B:");
        string C = Console.ReadLine();
        string D = "";

        if (B == "+")
            D = Convert.ToString(Convert.ToDouble(A) + Convert.ToDouble(C));
        if (B == "-")
            D = Convert.ToString(Convert.ToDouble(A) - Convert.ToDouble(C));
        if (B == "*")
            D = Convert.ToString(Convert.ToDouble(A) * Convert.ToDouble(C));
        if (O == "/")
            D = Convert.ToString(Convert.ToDouble(A) / Convert.ToDouble(C));

        Console.WriteLine("结果是:" + D);
    }     
}

小菜的代码有什么问题呢?
二 代码规范、重构

大鸟说:“且先不说出题人的意思,单就你现在的代码,就有很多不足的地方需要改进。比如变量命名,你的命名就是ABCD,变量不带有任何具体含义,这是非常不规范的;判断分支,你这样的写法,意味着每个条件都要做判断,等于计算机做了三次无用功;数据输入有效性判断等,如果用户输入的是字符符号而不是数字怎么办?如果除数时,客户输入了0怎么办?这些都是可以改进的地方。”

“哦,说得没错,这个我以前听老师说过,可是从来没有在意过,我马上改,改完再给你看看。”
代码如下:

class Program
{
static void Main(string[] args)
{
try
{
Console.Write("请输入数字A:");
string strNumberA = Console.ReadLine();
Console.Write("请选择运算符号(+、-、*、/):");
string strOperate = Console.ReadLine();
Console.Write("请输入数字B:");
string strNumberB = Console.ReadLine();
string strResult = "";

switch (strOperate)
{
case "+":
strResult = Convert.ToString(Convert.ToDouble(strNumberA) + Convert.ToDouble(strNumberB));
break;
case "-":
strResult = Convert.ToString(Convert.ToDouble(strNumberA) - Convert.ToDouble(strNumberB));
break;
case "*":
strResult = Convert.ToString(Convert.ToDouble(strNumberA) * Convert.ToDouble(strNumberB));
break;
case "/":
if (strNumberB != "0")
strResult = Convert.ToString(Convert.ToDouble(strNumberA) / Convert.ToDouble(strNumberB));
else
strResult = "除数不能为0";
break;
}

Console.WriteLine("结果是:" + strResult);

Console.ReadLine();


}
catch (Exception ex)
{
Console.WriteLine("您的输入有错:" + ex.Message);
}
}
}


大鸟:“吼吼,不错,不错,改得很快吗?至在目前代码来说,实现计算器是没有问题了,但这样写出的代码是否合出题人的意思呢?”

小菜:“你的意思是面向对象?”

大鸟:“哈,小菜非小菜也!”

三 复制VS复用

小菜:“我明白了,他说用任意一种面向对象语言实现,那意思就是要用面向对象的编程方法去实现,对吗?OK,这个我学过,只不过当时我没想到而已。”

大鸟:“所有编程初学者都会有这样的问题,就是碰到问题就直觉的用计算机能够理解的逻辑来描述和表达待解决的问题及具体的求解过程。这其实是用计算机的方式去思考,比如计算器这个程序,先要求输入两个数和运算符号,然后根据运算符号判断选择如何运算,得到结果,这本身没有错,但这样的思维却使得我们的程序只为满足实现当前的需求,程序不容易维护,不容易扩展,更不容易复用。从而达不到高质量代码的要求。”

小菜:“鸟哥呀,我有点糊涂了,如何才能容易维护,容易扩展,又容易复用呢,能不能具体点?”

大鸟:“比如说,我现在要求你再写一个windows的计算器,你现在的代码能不能复用呢?”

小菜:“那还不简单,把代码复制过去不就行了吗?改动又不大,不算麻烦。”

大鸟:“小菜看来还是小菜呀,有人说初级程序员的工作就是Ctrl+C和Ctrl+V,这其实是非常不好的编码习惯,因为当你的代码中重复的代码多到一定程度,维护的时候,可能就是一场灾难。越大的系统,这种方式带来的问题越严重,编程有一原则,就是用尽可能的办法去避免重复。想想看,你写的这段代码,有哪些是和控制台无关的,而只是和计算器有关的?”

四 业务的封装

小菜:“你的意思是分一个类出来? 哦,对的,让计算和显示分开。”

大鸟:“准确的说,就是让业务逻辑与界面逻辑分开,让它们之间的耦合度下降。只有分离开,才容易达到容易维护或扩展。”

小菜:“让我来试试看。”
代码如下:

class Program
{
static void Main(string[] args)
{
try
{
Console.Write("请输入数字A:");
string strNumberA = Console.ReadLine();
Console.Write("请选择运算符号(+、-、*、/):");
string strOperate = Console.ReadLine();
Console.Write("请输入数字B:");
string strNumberB = Console.ReadLine();
string strResult = "";

strResult = Convert.ToString(Operation.GetResult(Convert.ToDouble(strNumberA),Convert.ToDouble(strNumberB),strOperate));

Console.WriteLine("结果是:" + strResult);

Console.ReadLine();

}
catch (Exception ex)
{
Console.WriteLine("您的输入有错:" + ex.Message);
}
}
}

public class Operation
{
public static double GetResult(double numberA,double numberB,string operate)
{
double result = 0d;
switch (operate)
{
case "+":
result = numberA + numberB;
break;
case "-":
result = numberA - numberB;
break;
case "*":
result = numberA * numberB;
break;
case "/":
result = numberA / numberB;
break;
}
return result;
}
}


小菜:“鸟哥,我写好了,你看看!”

大鸟:“哈,孺鸟可教也,:),写得不错,这样就完全把业务和界面分离了。”

小菜心中暗骂:“你才是鸟呢。” 口中说道:“如果你现在要我写一个Windows应用程序的计算器,我就可以复用这个运算类(Operation)了。”

大鸟:“不单是Windows程序,Web版程序需要运算可以用它,PDA,手机等需要移动系统的软件需要运算也可以用它呀。”

小菜:“哈,面向对象不过如此。下会写类似代码不怕了。”

大鸟:“别急,仅此而已,实在谈不上完全面向对象,你只用了面向对象三大特性的一个,还两个没用呢?”

小菜:“面向对象三大特性不就是封装、继承和多态吗,这里我用到的应该是封装。这还不够吗?…………我实在看不出,这么小的程序如何用到继承。至于多态,其它我一直也不太了解它到底有什么好处,如何使用它。”

大鸟:“慢慢来,有的东西好学了,你好好想想吧,我要去“魔兽”了,改时聊。”

五 体会简单工厂模式的美妙

次日,小菜再来找大鸟,问道:“你昨天说计算器这样的小程序还可以用到面向对象三大特性?继承和多态怎么可能用得上,我实在不可理解。”

大鸟:“小菜很有钻研精神吗?好,今天我让你功力加深一级。你先要考虑一下,你昨天写的这个代码,能否做到很灵活的可修改和扩展呢?”

小菜:“我已经把业务和界面分离了呀,这不是很灵活了吗?”

大鸟:“那我问你,现在如果我希望增加一个开根(sqrt)运算,你如何改?”

小菜:“那只需要改Operation类就行了,在switch中加一个分支就行了。”

大鸟:“问题是你要加一个平方根运算,却需要把加减乘除的运算都得来参与编译,如果你一不小心,把加法运算改成了减法,这不是大大的糟糕。打个比方,如果现在公司要求你为公司的薪资管理系统做维护,原来只有技术人员(月薪),市场销售人员(底薪+提成),经理(年薪+股份)三种运算算法,现在要增加兼职工作人员的(时薪)算法,但按照你昨天的程序写法,公司就必须要把包含有的原三种算法的运算类给你,让你修改,你如果心中小算盘一打,‘TMD,公司给我的工资这么低,我真是郁闷,这会有机会了',于是你除了增加了兼职算法以外,在技术人员(月薪)算法中写了一句 
代码如下:

if (员工是小菜)
{
salary = salary * 1.1;
}

那就意味着,你的月薪每月都会增加10%(小心被抓去坐牢),本来是让你加一个功能,却使得原有的运行良好的功能代码产生了变化,这个风险太大了。你明白了吗?”

小菜:“哦,你的意思是,我应该把加减乘除等运算分离,修改其中一个不影响另外的几个,增加运算算法也不影响其它代码,是这样吗?”

大鸟:“自己想去吧,如何用继承和多态,你应该有感觉了。”

小菜:“OK,我马上去写。”
代码如下:

/// summary
/// 运算类
/// /summary
class Operation
{
private double _numberA = 0;
private double _numberB = 0;

/// summary
/// 数字A
/// /summary
public double NumberA
{
get{ return _numberA; }
set{ _numberA = value;}
}

/// summary
/// 数字B
/// /summary
public double NumberB
{
get{ return _numberB; }
set{ _numberB = value; }
}

/// summary
/// 得到运算结果
/// /summary
/// returns/returns
public virtual double GetResult()
{
double result = 0; 
return result;
}
}

代码如下:

/// summary
/// 加法类
/// /summary
class OperationAdd : Operation
{
public override double GetResult()
{
double result = 0; 
result = NumberA + NumberB;
return result;
}
}

/// summary
/// 减法类
/// /summary
class OperationSub : Operation
{
public override double GetResult()
{
double result = 0;
result = NumberA - NumberB;
return result;
}
}

/// summary
/// 乘法类
/// /summary
class OperationMul : Operation
{
public override double GetResult()
{
double result = 0;
result = NumberA * NumberB;
return result;
}
}

/// summary
/// 除法类
/// /summary
class OperationDiv : Operation
{
public override double GetResult()
{
double result = 0;
if (NumberB==0)
throw new Exception("除数不能为0。");
result = NumberA / NumberB;
return result;
}
}

小菜:“大鸟哥,我按照你说的方法写出来了一部分,首先是一个运算类,它有两个Number属性,主要用于计算器的前后数,然后有一个虚方法GetResult(),用于得到结果,然后我把加减乘除都写成了运算类的子类,继承它后,重写了GetResult()方法,这样如果要修改任何一个算法,都不需要提供其它算法的代码了。但问题来了,我如何让计算器知道我是希望用哪一个算法呢?”

大鸟:“写得很不错吗,大大超出我的想象了,你现在的问题其实就是如何去实例化对象的问题,哈,今天心情不错,再教你一招‘简单工厂模式',也就是说,到底要实例化谁,将来会不会增加实例化的对象(比如增加开根运算),这是很容易变化的地方,应该考虑用一个单独的类来做这个创造实例的过程,这就是工厂,来,我们看看这个类如何写。”
代码如下:

/// summary
/// 运算类工厂
/// /summary
class OperationFactory
{
public static Operation createOperate(string operate)
{
Operation oper = null;
switch (operate)
{
case "+":
{
oper = new OperationAdd();
break;
}
case "-":
{
oper = new OperationSub();
break;
}
case "*":
{
oper = new OperationMul();
break;
}
case "/":
{
oper = new OperationDiv();
break;
}
}

return oper;
}
}

大鸟:“哈,看到吧,这样子,你只需要输入运算符号,工厂就实例化出合适的对象,通过多态,返回父类的方式实现了计算器的结果。”
代码如下:

Operation oper;
oper = OperationFactory.createOperate("+");
oper.NumberA = 1;
oper.NumberB = 2;
double result = oper.GetResult();

大鸟: “哈,界面的实现就是这样的代码,不管你是控制台程序,Windows程序,Web程序,PDA或手机程序,都可以用这段代码来实现计算器的功能,当有一天我们需要更改加法运算,我们只需要改哪里?”

小菜:“改OperationAdd 就可以了。”

大鸟: “那么我们需要增加各种复杂运算,比如平方根,立方根,自然对数,正弦余弦等,如何做?”

小菜:“只要增加相应的运算子类就可以了呀。”

大鸟: “嗯?够了吗?”

小菜:“对了,还需要去修改运算类工厂,在switch中增加分支。”

大鸟: “哈,那才对,那如果要修改界面呢?”

小菜:“那就去改界面呀,关运算什么事呀。”

小菜:“ 回想那天我面试题写的代码,我终于明白我为什么写得不成功了,原来一个小小的计算器也可以写出这么精彩的代码,谢谢大鸟。”

大鸟: “吼吼,记住哦,编程是一门技术,更加是一门艺术,不能只满足于写完代码运行结果正确就完事,时常考虑如何让代码更加简炼,更加容易维护,容易扩展和复用,只有这样才可以是真的提高。写出优雅的代码真的是一种很爽的事情。不过学无止境,其实这才是理解面向对象的开始呢。给你出个作业,做一个商场收银软件,营业员根据客户购买商品单价和数量,向客户收费。”

小菜:“就这个?没问题呀。”

来源:http://www.tulaoshi.com/n/20160219/1599018.html

延伸阅读
标签: 面试 面试技巧
在面试过程中,招聘经理不会再花太多时间和精力从数量指标上去考察,而是通过交谈考察应聘者对于问题的定性分析水平。因此,临场发挥能力在面试中尤为重要。那么f一般面试是几个人?如何面试?下面给大家介绍面试人数分类,希望对大家有帮助。 一般面试是几个人 面试人数分类 (1)一对一:招聘人员与应聘者一对一的“过招”。作为最...
了解对象的类型 如果你想知道一个对象是否是特殊的类型,请编写下面的代码: If TypeOf obj Is ListItem Then ... 如果想得到对象类型的名字,使用: MsgBox "The item is a " & TypeName(obj) 控制长时间的循环 当程序陷入一个很长时间的循环中时,你就不可能点击任何一个命令按钮或者按键。...
《最终幻想13-2》怪物成长道具代码表一览 下面为大家带来《最终幻想13-2》怪物成长道具代码表一览,一起来看看吧。 修改方法: 道具列表在内存里是这样排列的 上图红线右侧就是需要修改的地方,如图所示基本上全是英文字母,每个道具从首字母开始占用17个字节,最后一个字节是数量,多余的就不说了,规律显而易见。 下面是代码:第...
标签: 牛奶 健康
口感香浓就是好牛奶吗? 面对市场上种类繁多、卖点各异的牛奶,中国奶协有关专家呼吁,为避免盲目消费,消费者在选购牛奶时应谨防三大误区。 误区一,常温奶和鲜奶营养一样。 专家指出,常温奶采用的是超高温灭菌法,即135摄氏度不少于TuLaoShi.com1秒的高温瞬时灭菌。这种方法在杀死牛奶中的有害病菌同时,也破坏了牛奶中的营养成分。而采...
标签: 理财 孩子
富裕家庭孩子如何学会理财? 很多富裕家庭的孩子对钱没什么概念,也不知道如何理财。正是由于不缺钱,反而让这些孩子不容易理解钱的价值和劳动所得之间的正确联系。对他们来说,钱是与生俱来的,它就总是在那儿。如果富裕家庭的父母不主动为孩子们建立金钱和劳动之间的正确关系,不教会他们如何尊重为得到钱而付出的劳动,孩子很容易会相信钱...

经验教程

640

收藏

20
微博分享 QQ分享 QQ空间 手机页面 收藏网站 回到头部