- 浏览: 877089 次
- 性别:
- 来自: 北京
文章分类
最新评论
-
Junjing:
非常感谢楼主的分享,受益匪浅!我是一位从业务规划和运营转需求分 ...
我们应当怎样做需求确认:评审与签字确认会 -
kersky:
感谢楼主的辛苦输出,半天看完了整个系列。对于一个转从开发转需求 ...
我们应当怎样做需求确认:评审与签字确认会 -
DEMONU:
必须要顶
谈谈软件开发的那些事儿 之 软件开发的轮回 -
dripstone:
非常感谢楼主,用了大半天的时间,一口气读完了需求分析阶段。好多 ...
我们应当怎样做需求确认:评审与签字确认会 -
Codepoe:
做了一些开发,看了楼主的文章,我深有感触,为自己的做法找到了理 ...
我们应当改变我们的设计习惯
事情总是这样的:当我们对一个遗留系统一忍再忍,再忍,忍,还要忍……终于积攒到某一天,实在忍无可忍了,拍案而起,不能再忍了,重构!!!事情就这样发生了。然而,在这时你突然发现,重构的工作千头万绪,真不知从何开始。堆积如山的问题此起彼伏,期望修改的设计思绪万千。这里有个想法,那里有个思路,什么都想做,却什么都做不了,真是脑子里一团乱麻。这时候,没有一个合理的步骤,清晰的计划,瞎干蛮干是十分危险的,它会为你的重构带来不可预期的未来。无数次的经验告诉我,不论是什么系统,采用什么架构,从分解大函数开始,肯定没有错。
大函数,就是那些业务逻辑特别复杂、程序代码特别多、一提起就叫人头疼不已的超级方法。超级大函数,很难让人读懂,更难于维护与变更,毫无疑问是软件退化的重灾区。它起初可能并不复杂,也逻辑清晰、易于读懂,但随着业务逻辑的一次次变更,不停地往里面添加代码,再加上一些不合理的设计,经过天长日久,变得越来越臃肿,超级大函数就这样产生了。超级大函数的产生是有它内在的客观原因的。怎么这么说呢?前面我们谈过,软件发展的客观规律就是业务逻辑越来越复杂。随着业务逻辑越来越复杂,正确的办法就是适时地重构和优化我们的代码。但非常遗憾地是,几乎很少有人认识到这一点。这样的结果就是,随着业务逻辑越来越复杂,人们总是就着原有的程序结构不停地往里面添加新的代码。原有的清晰而简单的程序,随着新代码的不断添加,开始变得越来越复杂而难懂了。正因为如此,在大多数软件企业的遗留系统中,超级大函数就变成了一种通病。让我们用HelloWorld为例来演变一番它的历程吧。
如前面第三章所述,最开初的HelloWorld程序是这样的:
了了数十行代码,简单明了。随后就开始变更了,首先是关于时间的问候变得复杂了,添加了一些特殊的节日的问题,如新年问候“Happy new year! ”、情人节问候“Happy valentine’s day! ”、三八妇女节问候“Happy women’s day! ”,等等。同时,对一天中的问候也变得更加精细。对于这样的需求,IT攻城狮们敲着键盘就开始改写了:
代码量开始翻倍。接着,客户要求所有的用户信息应当来源于数据库的用户表,同时设计了问候语规则表,所有关于时间的问候都应来源于对该表的查询。这时我们继续膨胀sayHello()这个方法:
这是一个十分简单的示例,但我们可以看到它已经由短短十来行膨胀成了60多行,膨胀了4倍之多。在最后这个版本中,sayHello()既要负责连接数据库、查询数据,又要获得当前时间的月份、日期与小时,还要完成相应的业务逻辑的判断,使程序变得相当复杂。我们可以继续想象,如果继续提出新的需求,比如支持多语言、支持多数据库,程序质量将继续下滑,直到我们无法忍受。
解决超级大函数问题最有效的办法就是分解,按照功能一步一步分解,还原其应有的优化结构。在这个过程中我们常用的重构方法叫“抽取方法(Extract Method)”。重构是一个探索的过程,因为我们总是起初对要重构的系统并不了解,没有设计文档(即使有,对不对还是一说呢),没有熟悉系统的人(哪怕只是在不明白的时候问一问)。你,只是接到任务才开始接手这个系统,你对这个系统的了解简直就是一猫黑。而这时,抽取方法是我们开始这种探索,了解这个系统最有效的工具,它往往是这样进行的:
当我们在阅读一段大函数时,我们可以自觉不自觉地为这些代码分段,为一段功能相对独立的代码编写注释。有时我们可能还需要调整代码的先后顺序,将一些有更多关联的代码放在一起,如将变量的声明与变量真正使用的代码放在一起,或者将有明显前后关系的代码放在一起。这样的调整是一个好的开端,因为它让我们的代码开始变得有序,开始变得可读。
随后,我们就可以使用“抽取方法”了。将被我们分段、加上注释的代码从原函数中抽取出来,放在另外一个独立的函数中,为这个函数取个易懂的名称——这是一个非常重要的好习惯。我常常为了给一个函数取一个正确的名字而思索很长时间,甚至修改好几次。不要认为这是在浪费时间,它也是优化代码重要的一个环节。但起初我们对这段代码的理解可能不那么深,因此我们往往选择用结果变量为其命名。随着我们对这段代码理解的深入,可以运用重构中的“重命名方法(Rename Method)”,根据其代码意图重新为其命名(许多开发工具,如eclipse,都支持该重构方法,它们使你在重命名的同时,同步修改了所有对该方法的调用)。经过这样对代码段的抽取,原代码在这里就变成了对这个新函数的调用。
举一个例子吧,这时一段真实的遗留系统,原有程序是这样写的:
这段代码写得并不好,有许多需要我们优化的地方。但记住“小步快跑”原则,此时不是解决其它问题的时候,现在我们首先是运用抽取方法优化程序结构。经过抽取以后,将以上加粗的部分改为了这样:
加粗的部分就是被改写的内容,同时将抽取的内容放进了一个独立的函数:
在给这个新创建的函数命名时,我们对这段代码的理解并不深刻。在原函数中,这段代码执行的结果是获得了lBlsj这个结果变量,即获得了办理时间。为此,我们先为该函数命名为getBlsj这个函数名。但在后续的工作中,我们逐渐理解到,它不仅可以获取办理时间,还可以获取很多时间。它实质是将时间由一种表示方式转换成另一种表示方式,因此我们运用开发工具的重命名功能,将该函数命名为transformDate,其它调用它的代码也随之修改了。重命名后的函数就不再仅仅运用在此处获取办理时间,还应用在其它业务代码的处理中。
抽取方法可大可小,你可以将一段数百行的代码抽取走,也可能只抽取了数行。但不论怎样,被抽取走的代码一定是功能内聚的,也就是说它们执行的是一个说得清道得明、清楚明确的功能。同时,被抽取走的代码一定是执行的一个清晰的功能,而不是多个。它可大可小,大也许还能分解为多个功能,但至少在逻辑上,这些功能是这个大的功能的组成部分。
抽取方法是一个探索的过程,最关键是那个红线划在哪里,即抽取代码的范围。多一行也对,少一行也对,关键在于我们抽取出来的这个函数,它的功能我们是怎样定义的。公说公有理,婆说婆有理,没有一个定论,所以方法的抽取常常是反反复复。开始我们按照一个思路抽取出来,后来想想觉得不对,因此又放回原函数,重新划分,重新抽取,反复多次。
有一次,我在重构一个Servlet的时候,抽取出来的函数是在执行一大段业务逻辑操作。但在完成了一系列业务操作以后,原程序要将返回值转换成二进制代码写入response中,返回前端。起初,我将整个这一段都抽取出来。但令人很别扭的是,传参的时候必须要把response传进去,这使得业务逻辑与Web应用环境耦合,不利于日后的优化,编写自动化测试。随后我还原了这段代码,重新进行抽取,将写response的部分留在了原函数中,而将红线画在了完成业务逻辑操作之后,写入response之前。新重构的函数得以与web应用解耦,为后面的进一步优化做好了准备。重构的过程,是考验开发人员能力的过程,需要大家反复练习与钻研。
另外,抽取方法就像核裂变,开始由一个函数裂变为几个函数。分解出来的函数又裂变为另外几个函数,不断这样往复下去。同时,抽取方法总是在一个类中发生裂变。而当这个类分解出来的方法达到一定程度以后,随之而来的就是类的裂变,由一个类分解成多个类,分解出来的类再分解……类的分解我们采用的是另一个方法——“抽取类(Extract Class)”,我们将在后面讲述。
重构是一系列的等量变换,抽取方法是这些等量变换中最典型的例子。将一段代码从原函数中抽取出来,代码依然是那些代码,只是程序结构发生了变换。正因为如此,才能保证我们的重构过程的安全可靠。
大话重构连载首页:http://fangang.iteye.com/blog/2081995
特别说明:希望网友们在转载本文时,应当注明作者或出处,以示对作者的尊重,谢谢!
大函数,就是那些业务逻辑特别复杂、程序代码特别多、一提起就叫人头疼不已的超级方法。超级大函数,很难让人读懂,更难于维护与变更,毫无疑问是软件退化的重灾区。它起初可能并不复杂,也逻辑清晰、易于读懂,但随着业务逻辑的一次次变更,不停地往里面添加代码,再加上一些不合理的设计,经过天长日久,变得越来越臃肿,超级大函数就这样产生了。超级大函数的产生是有它内在的客观原因的。怎么这么说呢?前面我们谈过,软件发展的客观规律就是业务逻辑越来越复杂。随着业务逻辑越来越复杂,正确的办法就是适时地重构和优化我们的代码。但非常遗憾地是,几乎很少有人认识到这一点。这样的结果就是,随着业务逻辑越来越复杂,人们总是就着原有的程序结构不停地往里面添加新的代码。原有的清晰而简单的程序,随着新代码的不断添加,开始变得越来越复杂而难懂了。正因为如此,在大多数软件企业的遗留系统中,超级大函数就变成了一种通病。让我们用HelloWorld为例来演变一番它的历程吧。
如前面第三章所述,最开初的HelloWorld程序是这样的:
/** * The Refactoring's hello-world program * @author fangang */ public class HelloWorld { /** * Say hello to everyone * @param now * @param user * @return the words what to say */ public String sayHello(Date now, String user){ //Get current hour of day Calendar calendar = Calendar.getInstance(); calendar.setTime(now); int hour = calendar.get(Calendar.HOUR_OF_DAY); //Get the right words to say hello String words = null; if(hour>=6 && hour<12){ words = "Good morning!"; }else if(hour>=12 && hour<19){ words = "Good afternoon!"; }else{ words = "Good night!"; } words = "Hi, "+user+". "+words; return words; } }
了了数十行代码,简单明了。随后就开始变更了,首先是关于时间的问候变得复杂了,添加了一些特殊的节日的问题,如新年问候“Happy new year! ”、情人节问候“Happy valentine’s day! ”、三八妇女节问候“Happy women’s day! ”,等等。同时,对一天中的问候也变得更加精细。对于这样的需求,IT攻城狮们敲着键盘就开始改写了:
/** * The Refactoring's hello-world program * @author fangang */ public class HelloWorld { /** * Say hello to everyone * @param now * @param user * @return the words what to say */ public String sayHello(Date now, String user){ //Get current month, date and hour. Calendar calendar = Calendar.getInstance(); calendar.setTime(now); int hour = calendar.get(Calendar.HOUR_OF_DAY); int month = calendar.get(Calendar.MONTH); int day = calendar.get(Calendar.DAY_OF_MONTH); //Get the right words to say hello String words = null; if(month==1 && day==1){ words = "Happy new year!"; }else if(month==1 && day==14){ words = "Happy valentine's day!"; }else if(month==3 && day==8){ words = "Happy women's day!"; }else if(month==5 && day==1){ words = "Happy Labor day!"; …… }else if(hour>=6 && hour<12){ words = "Good morning!"; }else if(hour==12){ words = "Good noon!"; }else if(hour>=12 && hour<19){ words = "Good afternoon!"; }else{ words = "Good night!"; } words = "Hi, "+user+". "+words; return words; } }
代码量开始翻倍。接着,客户要求所有的用户信息应当来源于数据库的用户表,同时设计了问候语规则表,所有关于时间的问候都应来源于对该表的查询。这时我们继续膨胀sayHello()这个方法:
/** * The Refactoring's hello-world program * @author fangang */ public class HelloWorld { /** * Say hello to everyone * @param now * @param user * @return the words what to say */ public String sayHello(Date now, long userId){ //Get database connection. try { Class.forName("oracle.jdbc.driver.OracleDriver"); } catch (ClassNotFoundException e1) { throw new RuntimeException("No found JDBC driver"); } String url = "jdbc:oracle:thin:@localhost:1521:helloworld"; String username = "test"; String password = "testpwd"; Connection connection; try { connection = DriverManager.getConnection(url,username,password); } catch (SQLException e1) { throw new RuntimeException("Connect database failed!"); } //Get current month, date and hour. Calendar calendar = Calendar.getInstance(); calendar.setTime(now); int hour = calendar.get(Calendar.HOUR_OF_DAY); int month = calendar.get(Calendar.MONTH); int day = calendar.get(Calendar.DAY_OF_MONTH); //Get the right words to say hello String words = null; String greetingRuleSql = "select words, month, day, hourLower, hourUpper from greeting_rules"; try { PreparedStatement statement = connection.prepareStatement(greetingRuleSql); ResultSet resultSet = statement.executeQuery(); while(!resultSet.isLast()){ int monthOfRule = resultSet.getInt("month"); int dayOfRule = resultSet.getInt("day"); if(month==monthOfRule && day==dayOfRule){ words = resultSet.getString("words"); break; } int hourLower = resultSet.getInt("hourLower"); int hourUpper = resultSet.getInt("hourUpper"); if(hour>=hourLower && hour<hourUpper){ words = resultSet.getString("words"); break; } } if(words==null) throw new RuntimeException("Error when searching greeting rules."); } catch (SQLException e1) { throw new RuntimeException("Error when getting greeting rules."); } //Get user's name String user = ""; String userSql = "select name from rms_user where user_id=?"; try { PreparedStatement statement = connection.prepareStatement(userSql); statement.setLong(1, userId); ResultSet resultSet = statement.executeQuery(); user = resultSet.getString(1); } catch (SQLException e) { throw new RuntimeException("Error when getting user's name."); } words = "Hi, "+user+". "+words; return words; } }
这是一个十分简单的示例,但我们可以看到它已经由短短十来行膨胀成了60多行,膨胀了4倍之多。在最后这个版本中,sayHello()既要负责连接数据库、查询数据,又要获得当前时间的月份、日期与小时,还要完成相应的业务逻辑的判断,使程序变得相当复杂。我们可以继续想象,如果继续提出新的需求,比如支持多语言、支持多数据库,程序质量将继续下滑,直到我们无法忍受。
解决超级大函数问题最有效的办法就是分解,按照功能一步一步分解,还原其应有的优化结构。在这个过程中我们常用的重构方法叫“抽取方法(Extract Method)”。重构是一个探索的过程,因为我们总是起初对要重构的系统并不了解,没有设计文档(即使有,对不对还是一说呢),没有熟悉系统的人(哪怕只是在不明白的时候问一问)。你,只是接到任务才开始接手这个系统,你对这个系统的了解简直就是一猫黑。而这时,抽取方法是我们开始这种探索,了解这个系统最有效的工具,它往往是这样进行的:
当我们在阅读一段大函数时,我们可以自觉不自觉地为这些代码分段,为一段功能相对独立的代码编写注释。有时我们可能还需要调整代码的先后顺序,将一些有更多关联的代码放在一起,如将变量的声明与变量真正使用的代码放在一起,或者将有明显前后关系的代码放在一起。这样的调整是一个好的开端,因为它让我们的代码开始变得有序,开始变得可读。
随后,我们就可以使用“抽取方法”了。将被我们分段、加上注释的代码从原函数中抽取出来,放在另外一个独立的函数中,为这个函数取个易懂的名称——这是一个非常重要的好习惯。我常常为了给一个函数取一个正确的名字而思索很长时间,甚至修改好几次。不要认为这是在浪费时间,它也是优化代码重要的一个环节。但起初我们对这段代码的理解可能不那么深,因此我们往往选择用结果变量为其命名。随着我们对这段代码理解的深入,可以运用重构中的“重命名方法(Rename Method)”,根据其代码意图重新为其命名(许多开发工具,如eclipse,都支持该重构方法,它们使你在重命名的同时,同步修改了所有对该方法的调用)。经过这样对代码段的抽取,原代码在这里就变成了对这个新函数的调用。
举一个例子吧,这时一段真实的遗留系统,原有程序是这样写的:
...... int iCtbz = -1; ElecObj elecObj = null; //获取办理时间 [b]int iCzyf = Integer.valueOf(Stream[9]).intValue(); String czMonth = String.valueOf(iCzyf % 20).toString().trim(); String czYear = String.valueOf( (iCzyf - (iCzyf % 20)) / 20 + 2000). toString(); if (czMonth.length() == 1){ czMonth = "0" + czMonth; } long lBlsj = Long.valueOf(czYear + czMonth).longValue();[/b] String dqyear = sysDate.toString().substring(0, 4); ......
这段代码写得并不好,有许多需要我们优化的地方。但记住“小步快跑”原则,此时不是解决其它问题的时候,现在我们首先是运用抽取方法优化程序结构。经过抽取以后,将以上加粗的部分改为了这样:
...... int iCtbz = -1; ElecObj elecObj = null; //获取办理时间 int iCzyf = Integer.valueOf(Stream[9]).intValue(); [b]long lBlsj = getBlsj(iCzyf);[/b] String dqyear = sysDate.toString().substring(0, 4); ......
加粗的部分就是被改写的内容,同时将抽取的内容放进了一个独立的函数:
/** *@param iCzyf *@return 获取办理时间 */ private long getBlsj(int iCzyf) { String czMonth = String.valueOf(iCzyf % 20).toString().trim(); String czYear = String.valueOf( (iCzyf - (iCzyf % 20)) / 20 + 2000). toString(); if (czMonth.length() == 1){ czMonth = "0" + czMonth; } return Long.valueOf(czYear + czMonth).longValue(); }
在给这个新创建的函数命名时,我们对这段代码的理解并不深刻。在原函数中,这段代码执行的结果是获得了lBlsj这个结果变量,即获得了办理时间。为此,我们先为该函数命名为getBlsj这个函数名。但在后续的工作中,我们逐渐理解到,它不仅可以获取办理时间,还可以获取很多时间。它实质是将时间由一种表示方式转换成另一种表示方式,因此我们运用开发工具的重命名功能,将该函数命名为transformDate,其它调用它的代码也随之修改了。重命名后的函数就不再仅仅运用在此处获取办理时间,还应用在其它业务代码的处理中。
抽取方法可大可小,你可以将一段数百行的代码抽取走,也可能只抽取了数行。但不论怎样,被抽取走的代码一定是功能内聚的,也就是说它们执行的是一个说得清道得明、清楚明确的功能。同时,被抽取走的代码一定是执行的一个清晰的功能,而不是多个。它可大可小,大也许还能分解为多个功能,但至少在逻辑上,这些功能是这个大的功能的组成部分。
抽取方法是一个探索的过程,最关键是那个红线划在哪里,即抽取代码的范围。多一行也对,少一行也对,关键在于我们抽取出来的这个函数,它的功能我们是怎样定义的。公说公有理,婆说婆有理,没有一个定论,所以方法的抽取常常是反反复复。开始我们按照一个思路抽取出来,后来想想觉得不对,因此又放回原函数,重新划分,重新抽取,反复多次。
有一次,我在重构一个Servlet的时候,抽取出来的函数是在执行一大段业务逻辑操作。但在完成了一系列业务操作以后,原程序要将返回值转换成二进制代码写入response中,返回前端。起初,我将整个这一段都抽取出来。但令人很别扭的是,传参的时候必须要把response传进去,这使得业务逻辑与Web应用环境耦合,不利于日后的优化,编写自动化测试。随后我还原了这段代码,重新进行抽取,将写response的部分留在了原函数中,而将红线画在了完成业务逻辑操作之后,写入response之前。新重构的函数得以与web应用解耦,为后面的进一步优化做好了准备。重构的过程,是考验开发人员能力的过程,需要大家反复练习与钻研。
另外,抽取方法就像核裂变,开始由一个函数裂变为几个函数。分解出来的函数又裂变为另外几个函数,不断这样往复下去。同时,抽取方法总是在一个类中发生裂变。而当这个类分解出来的方法达到一定程度以后,随之而来的就是类的裂变,由一个类分解成多个类,分解出来的类再分解……类的分解我们采用的是另一个方法——“抽取类(Extract Class)”,我们将在后面讲述。
重构是一系列的等量变换,抽取方法是这些等量变换中最典型的例子。将一段代码从原函数中抽取出来,代码依然是那些代码,只是程序结构发生了变换。正因为如此,才能保证我们的重构过程的安全可靠。
大话重构连载首页:http://fangang.iteye.com/blog/2081995
特别说明:希望网友们在转载本文时,应当注明作者或出处,以示对作者的尊重,谢谢!
发表评论
-
大话重构连载19:大对象的演化过程
2014-11-17 09:18 2240很好,我们终于迈出了 ... -
大话重构连载18:最常见的问题
2014-11-02 17:15 2224使用抽取方法,虽然道 ... -
大话重构连载17:抽取方法的实践
2014-10-29 08:57 2353说了那么多理论,我们来看看怎样使用抽取方法来重构遗留系统。如前 ... -
大话重构连载15:采用Mock技术完成测试
2014-09-21 11:08 2294第五次重构我们引入了数据库的设计,用户信息要从数据库中读取,问 ... -
大话重构连载14:我们是这样自动化测试的
2014-09-18 00:52 2315说了那么多,让我们用示例看看,系统重构是应该怎样做自动化测试的 ... -
大话重构连载13:自动化测试——想说爱你不容易
2014-09-09 10:48 2469正如许多事情都有其两面性一样,测试方法也是这样。要保证测试方法 ... -
大话重构连载12:你不能没有保险索
2014-09-08 06:59 1989通过前面的描述你已经 ... -
大话重构连载11:小步快跑是这样玩的
2014-08-26 10:29 2356说了那么多,相信你对 ... -
大话重构连载10:小设计而不是大布局
2014-08-10 14:39 2358开车的朋友一定深有体 ... -
大话重构连载9:大布局你伤不起
2014-08-04 10:03 2134作为优秀开发人员,重 ... -
大话重构连载8:盘点我们的重构工具箱
2014-07-27 21:55 2595下面我们来盘点一下系统重构工具箱里都有什么,也就是看一看系统重 ... -
大话重构连载6:一个真实的谎言
2014-07-22 08:46 2211经过前面的一番讲解,相信你已经对系统重构有了一些初步的认识了。 ... -
大话重构连载7:重构是一系列的等量变换
2014-07-22 08:37 2131毫无疑问,系统重构是 ... -
大话重构连载5:软件修改的四种动机
2014-07-09 10:58 2486软件,自从被我们开发出来并交付使用以后,如果它运行得好好的,我 ... -
大话重构连载4:大布局与小步快跑
2014-07-07 17:47 2521以往我们在重新设计一个系统时,总是喜欢大布局。全面地整理系统需 ... -
大话重构连载3:在保险索上走钢丝
2014-07-02 19:06 2508当我们开始系统重构的时候,不是着手去修改代码,而是首先建立测试 ... -
大话重构连载2:什么是系统重构
2014-06-29 04:19 3356前面我们提到了,面对软件工业时代的到来,我们的软件企业陷入了一 ... -
大话重构连载首页
2014-06-18 08:59 7956《大话重构》这本书是我写的第一本书,从今天起我将通过连载的 ... -
大话重构连载1:遗留系统——软件工业时代的痛
2014-06-18 08:49 3601我常常感到幸运,我们 ...
相关推荐
"《大话统计学》R 程序包-函数" 《大话统计学》R 程序包-函数是一本关于统计学和 R 语言的教材,涵盖了统计学的多个方面,包括描述统计、概率理论、随机变量、概率分布、抽样理论、区间估计、假设检验等。该书章节...
Android之大话设计模式——:抽象工厂模式借鉴.pdf
大话Oracle RAC:集群、高可用性、备份与恢复(带目录清晰中文完整版)
大话Oracle RAC:集群、高可用性、备份与恢复。 此书被认为不可多得的好资料之一:大话Oracle RAC(PDF经典),看完之后深有感触,发出来共享一下。
Android之大话设计模式——:抽象工厂模式参考.pdf
初中语文文摘历史“大话王”郭台铭:被夏普狠狠摔了个大跟
《大话Oracle.RAC:集群、高可用性、备份与恢复(第2版)》是一部深入探讨Oracle数据库Real Application Clusters(RAC)技术的专业书籍,主要围绕Oracle RAC的集群架构、高可用性策略以及数据库的备份与恢复策略...
《大话移动APP测试》是一本详尽介绍Android与iOS平台测试应用的指南,旨在帮助读者深入理解并掌握移动应用的测试技术。本书全面覆盖了移动端的测试领域,包括平台特性、设备兼容性、功能测试、性能测试、安全测试等...
《大话存储:存储系统底层架构原理极限剖析(终极版)》是一本深入探讨存储技术的专业书籍,由一位对技术充满热情的作者精心撰写。这本书以其严谨的态度和丰富的想象力,揭示了存储系统的底层奥秘,旨在帮助读者全面...
《大话Oracle RAC:集群 高可用性 备份与恢复》可以作为数据库开发人员、数据库管理员、数据库初学者及其他数据库从业人员的工作参考手册,也可以作为大中专院校相关专业师生的参考用书和相关培训机构的培训教材。
《大话数据分析:Tableau数据可视化实战》的数据集是一份重要的资源,对于想要学习和提升Tableau数据可视化技能的人来说极具价值。Tableau是一款强大的商业智能工具,它允许用户通过直观的拖放界面来探索和可视化...
大话存储:存储系统底层架构原理极限剖析(终极版)第4部分 大话存储:存储系统底层架构原理极限剖析(终极版)第4部分
目前国内阐述网络存储的书籍少之又少,大部分是国外作品,对存储系统底层细节的描述不够深入,加之术语太多,初学者很难真正理解网络存储的精髓。《大话存储2:存储系统架构与底层原理极限剖析》以特立独行的行文风格...
大话存储:存储系统底层架构原理极限剖析(终极版)第3部分 大话存储:存储系统底层架构原理极限剖析(终极版)第3部分大话存储:存储系统底层架构原理极限剖析(终极版)第3部分
大话Java:从零基础到数据库、Web开发以漫画的形式,由浅入深、循序渐进地介绍Java编程的常用技术和方法,内容涵盖了Java基本语法结构、面向对象特征、集合框架体系、异常处理、GUI编程、MySQL数据库、JDBC数据库...
[大话·实战] 大型OA+HRM企业管理系统原型公众号:产品大鹿
大话存储:存储系统底层架构原理极限剖析(终极版)_张冬2015.01_P989
大话移动APP测试 Android与iOS应用测试指南