




已阅读5页,还剩23页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
更佳编程之路:简介与第 1 章 任何软件开发小组的成功或失败很大程度上取决于团队精神。对于构思良好而又不断变化的指导思想而言,从经理到成员,团队是否是一个整体是由它各个部分的和谐程度决定的。在打破完美程序员的神话后,Teodor 将解散原有的缺乏创见的软件开发小组,然后再把它建设为一个和谐的,有活力的整体。欢迎来到 developerWorks 阅读全面指导如何更好地用 Perl 编程的系列文章。在这一系列文章的第一部分里,Teodor 介绍了他写的书,并以一种崭新的观点来论述编码指南。这是一本适合 Perl 初学者和中级程序员的书。但即使是 Perl 高级程序员,在看了第 1 部分的技巧、第 2 部分的项目管理工具以及第 3 部分的 Parse:RecDescentsource 代码分析脚本之后,也会发现这些章节的大部分内容是令人兴奋又相当有用的。程序和脚本这两个词在这里被交替使用。在 Perl 语言中,二者差不多是一回事儿。程序实际上可以由许多脚本组成,而脚本也可以包含许多程序,但为了简单起见,在使用这两个词时我们这样理解:一个脚本文件只包含一个程序。本书的用途第 1 部分主要介绍能提高您 Perl 编程技能的技巧,从最佳编程实践到代码调试都有讨论。它并不教您 Perl 编程。介绍 Perl 编程的书有很多,但这些书在讲解的清晰和全面方面确实做得不很好。第 2 部分将告诉您如何利用标准的软件项目管理工具更好地管理一个较小的 Perl 软件开发小组。Perl 程序员经常形象化地把软件开发小组称作“一群猫”。第 2 部分将把项目管理工具应用于一个较小的(2 至 6 人)Perl 开发小组,并将研究成功地管理这样的小组与传统的项目管理方法有何不同。第 3 部分将开发那些分析源代码的工具(将开发 C 和 Perl 的样本)并帮助您更好地管理小组的工具。今天源代码的分析充其量还只是表面的,这些分析包括从明显的和不相关“代码行”度量方法到函数点(function point)(请参阅文章后面的参考资料),它们对于理解程序员的思想倾向没有帮助。理解程序员的思想倾向将是第 3 部分的目标。将开发一些工具来跟踪度量方法,如:注释的可读性和一致性、代码的复用性和代码的可读性。这些度量将作为软件项目的一部分而不是它的目标来介绍。没有完美的编程,只有对完美的不断追求。好的程序员每天都会学到新的东西,并且不断提高他们的技术与技能。呆板和僵化永远都是独创性和创造性的天敌。追求完美程序员最常犯的错误并不在他的程序错误列表中。它与程序员的年龄或选择的程序语言没有任何关系。这个最常犯的错误就是误以为自己具有了所有的能力而且根本 没有提高的余地了。这可以说成是人的天性;但我认为人的天性是对知识与提高的不断追求。可是,傲慢自大和害怕犯错使我们止步不前。抵制这两种坏习性不仅可以使我们成为更好的程序员,还可以使我们成为更好的人。我相信,相互沟通以及人员素质是创建成功的软件开发团队的关键,这比任何其它因素都重要。不断提高程序员的技能和接受批评的能力对一个软件开发团队的成员来说是基本的要求。这些要求优于所有其它的要求。回想一下您上一次改变风格的情况。您是运用了学到的新算法,还是新的注释风格,或仅仅是以另一种方法命名变量?不管是什么,对于完成代码并使它完美这个目标而言,它只不过是前进路途上的一步而不是结束。不应该要求程序员一字一句地遵守精确的编码指南;而程序员也不应该为了完成任务而仓促应付这些指南。想象一个管弦乐队 既不能是一些死气沉沉、毫无激情的演奏者,也不能是一些草率地即兴发挥的演奏家(尽管后者更容易受到欢呼)。一个死气沉沉的演奏者仅仅照着乐谱而没有把精力和感情投入到音乐中;演奏家则必须约束自己,不能在演奏时随意尝试新的旋律片段或按自己的节拍演奏。演奏和谐的音调编码指南更象一个音乐家遵守的书面格式的乐谱 何时演奏、何时停止、演奏得多快、什么节拍等等。而音调本身,打个不恰当的比方,就是项目的目标 有时是独立的高音,有时则是乐器的和声。在管弦乐队中,指挥负责指挥但并非告诉每个乐手应该如何演奏,每个人都是整个演奏的一部分。指挥负责协调。因为音乐在编程技术出现以前已经存在了好几个世纪,所以或许有值得借鉴的经验。软件项目经理既不是“暴徒”也不是“越狱的罪犯”。她和其他的人一样是团队的一个成员。不必盲目地把本系列文章所介绍的指南摘录出来作为正式的编码原则。您项目的编码标准就是您自己的,它们真实反映了您自己的“管弦乐”的乐谱。不要强求程序员们做得完全正确,这样只会造成一种不信任和畏惧的气氛。对于极小的错误,您不必在代码重审或追究责任上斤斤计较。取而代之的是,介绍这些指南并且观察大家的反应。如果没人采用您喜欢的注释格式,那它可能就是一种不好的格式。如果您觉得大家编写程序时显得不太聪明,那么可能是您编写指南时太过聪明。如果您认为人人都应该使用的调试器呆在布满灰尘的房间里还没被拆开,那么请重新考虑对 Whizzo Debugger 3.4 的需求。或许每个人都因为某种原因更喜欢使用 Acme Debugger 1.0。当然,程序员有时会毫无原由地很固执,其实只是不愿变革。很难使人们相信 20 年的经验并不能给他们组织有序的思想。另一方面,刚参加工作的大学毕业生经常会显得缺乏自信。承认并适应这些特征,以及团队中所有其它的特征。把想法介绍给较顽固的经验丰富的成员时,要使他们觉得自己是在帮着做这件事。通过指导和支持使大学毕业生树立起信心,直到他们能独立发挥作用。所有的这些,难道只是为了几条编码指南吗?编码指南对于一个软件团队,正如指挥和和声对于音乐一样,是基本的。它们产生一致性和凝聚力。新成员会感觉受到欢迎并能更快地融入到团队中。老成员则将更乐意接纳这些新来者。如果缺少一位成员,正如因为一个人不能理解另一个人的代码,将不会使项目限于瘫痪境地。请牢记速度并不是衡量程序代码改进的唯一尺度。对于任何软件项目,特别是长期项目,易测试、易编制文档和易维护也是很重要的。象 Perl 这样灵活的语言有助于在软件项目的各个阶段中进行良好的编码。尽管本书关注的是 Perl,但其中的许多原则也适用于其它语言,如 C、C+、Java 和 Python。最后,请做一个革新者。不论您在团队中的职位 经理或成员 请不断寻找新的想法并实践它们。完美或许不太可能,但它却是值得尝试的目标。革新者是团队的真正力量,如果没有他们那么悦耳的音调很快会变得乏味。和同行保持联系;不断从他们那儿学习新东西。象 Usenet 那样的媒体(请参阅参考资料)是交换意见的好地方。相互指导,相互学习。请记住一点,总会有提高的余地。最重要的,开心一点,然后让音乐响起。更佳编程之路: 第二章 developerWorks 上的这个系列文章包含使用 Perl 进行更佳编程的完整指南。在这第二部分中,Teodor 仔细研究了代码中的注释。对于软件团队的长期目标来说,程序代码中的注释可能与实际代码本身一样重要。不幸的是,它们经常也是最容易被忽视的。通过技巧、讲解、示例和轶事,Teodor 从头到尾深入讨论了对程序语言进行注释的迫切本质。从来没有文档太多这回事。清晰常常意味着重复。将您的代码看成是向世界展示的东西。世界上有许多人。您认为冗余的注释可能对某人大有用处。五年之后,当您添加新特性时,它甚至可能对 您大有用处。基本注释编写程序时,使用良好的规划。不必事先确定每个细节,但是应该将程序分成几个组成部分,并且使用注释来填充间隙。下列是我个人的编码风格。您可能不喜欢它,但是请客观地看待它,并且看看有什么可以为您及您的团队所用。首先,考虑一下该注释的预期读者。尽量使注释足够清晰,以便第三方顾问领会。代码越复杂,就应该添加越多的注释以阐明目的。不要将注释放在以后再做;让它们成为您思考过程的一部分:问题、解决方案、注释,然后调试。在调试之前创建注释尤为重要。您自己代码中的注释将有助于更好和更快地调试。有时不仅陈述问题的解决方案很有帮助,而且陈述问题本身也是很有帮助的。例如:清单 1 # function: do_hosts# purpose: to process every host in the /etc/hosts table and see if it# resolves to a valid IP# solution: read the list of hosts as keys in a hash, then go through# the list of keys (hosts) and store the IP address for each host as# the value for that key, or undef() if it doesnt resolve properly.# Return a reference to the hash, or undef if the /etc/hosts file was# not accessible. 推荐简化为:清单 2 # function: do_hosts: process every host in the /etc/hosts table and see if it# resolves to a valid IP; return a reference to the hash (key=host,# value=IP or undef), or undef if the /etc/hosts file was not accessible. 还有另一种方法:清单 3 # do_hosts: returns a ref to hash of hosts (key=host, value=IP/undef)# from /etc/hosts 以上方法都有效,这取决于 do_hosts() 的复杂程度。如果函数只有两行,则不要浪费时间来写三段注释。但是,如果函数有几页,则不要吝惜说明。注释程序的开头程序应该以对其目的简要说明来开头。不要让人们滚动几页才能弄清您在做什么。如果正在使用版本控制系统(例如,CVS),则将适当的头(例如,ID 头)放在文件的开头。尽量简练。两行,最多四行,应该足以对程序进行简要描述。给出联系人姓名、电子邮件、电话号码或团队联系方式。清单 4 #!/usr/bin/perl -w# whodunit.pl: A script to solve a murder mystery# by $Id: whodunit.pl,v 1.92 2000/08/08 19:08:50 joe Exp $ 第一行上的注释是大多数 UNIX 系统上的一种标准方法,用来表示执行脚本时运行哪一个应用程序(在“!”后的所有东西被认为是解释器名称)。-w 标志表示打开警告标志 总是一个很好的主意,即使对于很有经验的程序员来说。第二行(第一个注释行)是程序及其目的的简短描述。第三行(第二个注释行)给出了作者姓名和唯一标识文件的发布日期和版本的 ID 头。RCS 和 CVS 特别地使用 ID 头,它在提交脚本时自动更新。有关 RCS 和 CVS 的更多信息,请参阅本文后面的参考资料。注释初始化节初始化节应该在逻辑上和物理上与程序的开头分开,例如,可以通过额外注释或让它处于文件的开头来实现。初始化节,与上面描述的程序的开头相反,包含程序启动时执行的实际代码。在 Perl 中,初始化节应该由下列部分组成(最好按该顺序):模块和编译指示 常量 BEGIN/END/INIT/CHECK 子例程 初始化代码 模块和编译指示Perl 中的 use 关键字命令解释器装入模块或者打开编译指示(“no pragma”关闭编译指示)。编译指示将解释器引入正确的方向。例如, use utf8 告诉解释器要准备好 UTF-8 编码的数据文件和流。使每个模块的注释在水平方向上排列整齐,并且每个模块或编译指示有一个注释,是大有好处的。清单 5 use Data:Dumper; # for debugging printoutsuse strict; # be strict - pragma for the interpreteruse POSIX; # use the POSIX functions 第一次这样做时,只需要复制和粘贴就可以将模块和编译指示放入新程序。我建议“strict”编译指示。此外,它还将确保您诚实地声明变量,以我的经验,这是 Perl 中的错误源,就象内存分配是 C/C+ 中的错误源一样。请使用 perldoc 命令来察看所有模块和编译指示文档。例如,perldoc strict 说明了有关 strict 编译指示的所有信息 它可以做什么,如何使用,等。某些编辑器具有总是将注释放在特定位置的良好能力(在 Emacs 中,indent-for-comment 命令自动完成该操作)。您自己应该彻底熟悉编辑器的命令。这值得花时间。常量虽然可以作为另一个 Perl 编译指示来查看常量,但是它们应该有自己的节。它们的注释应该类似于模块和编译指示的注释,但是,如果将箭头对齐,将更好看:清单 6 use constant ALPHA = 1; # alpha codeuse constant BETA = 2; # beta codeuse constant GAMMA = 3; # gamma codeuse constant USER = 4; # user ID offsetuse constant GROUP = 5; # group ID offsetuse constant DEPT = 6; # dept. ID offset BEGIN/END/INIT/CHECK 子例程象注释常规子例程一样注释 BEGIN/END/INIT/CHECK 子例程(有关更多信息,请参阅 perldoc perlmod)。在文件的任何位置都可以创建它们,并且有可能多次定义它们。我建议将它们放在文件的开头或结尾,这样易于找到它们。请注意只有一行的 BEGIN 函数不需要详尽的注释。清单 7 # BEGIN: executed at startup, assigns root to the USER environment variableBEGIN $ENVUSER = root; 初始化代码实际代码出现在初始化节的最后。同样,如果可能,在单个块中将注释对齐。清单 8 $| = 1; # auto-flush the output$Data:Dumper:Terse = 1; # produce human-readable Data:Dumper output# define the configuration variablesmy $config = AppConfig-new();$config-define( # list of undo commands UNDO = ARGCOUNT = ARGCOUNT_LIST , # file to log data LOG_FILE = ARGCOUNT = ARGCOUNT_ONE , );$config-file(whodunit.conf); # load the whodunit configuration file 初始化代码打开自动刷新(auto-flushing)显示(因此,将立即显示输出),然后告诉 Data:Dumper 模块产生可读的输出,最后创建 AppConfig 配置。注释常规代码注释常规代码很容易。如果可能,只要将注释对齐,尽量简练,当事情不清楚时,不要害怕深入解释它们。清单 9 print Dumper %ENV; # print the full ENV hash# get the environment variable names that begin with USERuser_vars = grep(/USER/, keys %ENV); # print the values in all the variables that begin with USER, using a# hash sliceprint Dumper ENVuser_vars; print Donen; # print done message# TODO: find better method of sorting variables# TODO: use Data:Dumper with variable names 请注意,注释从第 0 列或第 40 列开始。一致性使注释更具可读性。同样,必要时,多行注释会更好。还可以使用注释来记录在哪里发生了功能丢失、错误或不完整。如果想遍历所有代码并且查看还有哪些东西不完整,则“TODO”字很有用 一个快速的 grep 命令将打印出所有 TODO 项。不必注释每行代码,但是,请记住当调试或扩展程序时,注释是一种最好的资源。任何其它来源的程序员文档往往落后于实际代码,除非程序员非常勤奋。注释循环和条件语句应该象注释常规代码和函数一样注释循环和条件语句。对循环编号以便以后标识它们似乎有些过分。更好的方法是使用折叠编辑器,折叠循环时,它可以将整个循环显示成一行(折叠标记之间的行是隐藏的,但它们仍然存在)。考虑一下如 XML/HTML 开始结束标记这样的折叠的标记,它们是可以嵌套的。您最喜爱的编辑器可能已经支持折叠。(X)Emacs 就支持折叠,它不是使用 Outline 就是使用 folding.el 方式。清单 10 # go through all the numbers between 2 and 200, and print a message# for each oneforeach my $counter (2 . 200) print Whoa, the counter is $counter!n; 总是陈述循环的目的和边界。例如,“count from 2 to 200”很好,但是“process array”则不好。如果逻辑条件影响边界,同样陈述它们,但不在循环的顶部。循环顶部的摘要不应该记录常规迭代的异常,除非它们对循环非常重要。您自己判断吧。注释程序的最终阶段在许多方面,程序的结尾是最繁琐的。工作已经完成,数据结构已经进入休眠状态(在 Perl 中不必担心内存释放),并且现在离结尾只差几行了。不要让这愚弄了您 程序的结束行可能与其它行一样危险。在这里,注释最不起眼的行,因为调试程序员所做的第一件事就是查看程序的退出行为。清单 11 # delete old files, warn if they cant be removedforeach (myfiles) unlink $_ or warn Couldnt remove $_: $!;print whodunit.pl is done!n; # tell the user were doneexit; # exit peacefully 编写程序的 POD 文档和帮助旧式纯文档(Plain old documentation (POD))是将 Perl 脚本放入脚本本身的一种方法。perldoc perlpod 命令将告诉您有关 POD 文档及其语法的更多信息。好的 POD 文档意味着用户可以快速和有效地访问程序的帮助。花时间学习 POD 语法:编写手册将更容易。另外,POD 与各种手册格式化程序兼容,因此可以从同一文档生成一个纯文本文件、UNIX 风格的帮助手册页和专业 LaTeX 文件。POD 是一个十分有限的格式,但是足够满足大多数文档的需要。通常,下列节应该出现在 POD 文档中:NAME、SYNOPSIS、DESCRIPTION、OPTIONS、RETURN VALUE、ERRORS、DIAGNOSTICS、EXAMPLES、ENVIRONMENT、FILES、CAVEATS/WARNINGS、BUGS、RESTRICTIONS、NOTES、SEE ALSO、AUTHORS 以及 HISTORY(从 perldoc pod2man 中可以找到有关每节的更多信息;请记住这些是建议而不是命令)。某些程序员对其程序设置 -h 开关以对程序调用 perldoc,因此打印出 POD 文档,就象用户输入 perldoc whodunit.pl 一样。这里的问题是用户不想从 -h 开关获取更多的额外信息。他只想要选项的摘要和列表。因此,更好的方法是编写从使用 -h 开关产生的单独帮助处理程序。清单 12 # print_help: help handler, prints out help for whodunit.pl and exitssub print_help # print the help itself print 0); 在上例中,$i 在执行 grep 之前没有值,因此,我们可以使用一个 do-until 循环并采用一种更自然的流程,而不是给它赋一个特殊的假值(fake value)然后再使用 while。当然,不使用“until”而使用“while”也是可以的。最后,与 do-until() 和 while() 循环采用一个参数不同的是,for() 循环采用三个参数。这就是 for() 循环最受世界各地的 C 程序员青睐的原因所在 可以很轻易地编写一个在一行中做所有事的 for() 循环。甚至有时程序员还编写没有循环体的 for() 循环,因为所有工作都在循环参数自身内部完成。这种压缩的代码只有在代码模糊大赛(obfuscation contest)中才受欢迎,在生产中却不受欢迎。for() 有一个初始化区(令人惊讶的是,初始化区常用于初始化变量)、一个迭代区(通常在这里递增循环计数器)和一个测试区(用于测试是否应该继续执行循环)。因此,等价的 while() 和 for() 循环将是:清单 3. 等价的 for() 和 while() 循环 for ($i=0; $i+; $i 10) # do something 10 times$i=0;while ($i 3; # 3 children per processuse constant PI = 3.14; # 2 digits of precisionuse constant MESSAGE = Hello; # a message 在调用函数之前先制订函数原型。制订函数原型很容易。“perldoc perlsub”帮助页面将帮助您更好地理解原型。制订原型对于任何程序员来说都是个好习惯,因为它可以显示出预想(forethought)并在特定情况下帮助您避免编译器错误。在函数更改时更改原型可能令人乏味,但是这里的问题在于在一开始就没有定义好函数。在编码之先制订计划,在制订计划之前先思考。每隔 10 分钟思考一下代码将采取的途径将为您节省 1 小时的编码和调试时间。Perl 自动在字符串和数字之间转换。不能也没有必要关闭这项特性,但是 Perl 程序员新手可能在几小时内就会产生出新的让人感到意外的错误。请阅读 Perl FAQ(请参阅参考资料)、Programming Perl 或 Learning Perl 书籍,当然还有“perldoc perldata”帮助页面(“Scalar values”部分)。练习编写一个从 1 数到 100 的循环,并且 打印所有偶数 打印所有奇数 打印所有以 1、2 或 7 结尾的数字 编写一个从 100 向后数到 1 的循环编写一个程序,该程序读取标准输入并打印以“hello”(大写、小写或大小写混合均可)开始的所有行。提示:请查看“perldoc perlrun”帮助页面,尝试使用 Perl 的内置开关,而不要编写自己的循环。编写一个逻辑条件,该条件将检查一个标量是否已定义、是否是非零、回文(向前读和向后读都一样)或者是数字 234.98。分别使用和不使用 Perl 解释器的“-w”标志和“use strict”编译器编译参数各测试一次。如果生成了警告信息,您是否理解这些警告信息?以通常(不带后缀)的表示法编写以下代码,如果可以,则简化它们:print if $debug; $i+ unless $i 10; unless ($j & !$i) $j += $i while ; next while ; 函数型编程 developerWorks 上的这个文章系列包含一个有关如何用 Perl 进行更佳编程的完整指南。在本文(也就是第四部分)中,Teodor 介绍了函数型编程和几个基本的 Perl 习惯用法,如 map() 和 grep() 函数以及 Schwartzian 和 Guttman-Rosler 变换,它们对于那些追求代码的执行速度和优雅性的 Perl 程序员来说十分重要。函数型编程(FP)可以是一种方法、解决方案或信仰。我更愿意它是除信仰之外的什么东西,什么东西都行,因为它应该是只是程序员工具箱中的一件工具。来自“comp.lang.functional FAQ”函数型编程是一种强调对表达式求值而不是执行命令的编程风格。通过使用函数来组合基本值以形成表达式。函数型语言是一种支持和鼓励以函数风格编程的语言。 如果我们进行掩饰那么它就很好正如我经常提到的一样,不应该只因为某种工具在那儿并且您可以用而使用它。锤子用来钉钉子;螺丝刀用来拧螺丝。了解您全部的工具并使用合适的工具;您的生活会因此轻松很多。对函数型编程的一种简单看法是:函数型编程只是将函数应用到值,而过程型(传统)编程则是为函数的副作用(side effect)而使用函数。comp.lang.functional FAQ(请参阅本文稍后的参考资料)中引用的参考资料是理解函数型编程方法和意图的最佳起点。Perl 本质上是过程型语言,在 Perl 中,函数型编程只能是一种方法。实际解决方案一般都使用 map() 或 grep() 函数来模拟函数型解决方案。函数型编程方法之所以有价值有三个原因。首先,FP 可以使程序员以全新的角度看待问题,并可能因此想出更好的解决方案。第二,没有 map() 和 grep() 的帮助,Schwartzian 和 Guttman-Rosler 变换以及很多其它 Perl 习惯用法都难以使用和理解。第三,因为 Perl 中的函数调用消耗系统资源相当多,所以,不使用 map() 和 grep() 而
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025内蒙古鄂尔多斯生态环境职业学院专业技术人员招聘18人考前自测高频考点模拟试题含答案详解
- 2025河南新乡市牧野区世青学校招聘考前自测高频考点模拟试题及答案详解(名校卷)
- 2025贵州遵义市赤水市第一批就业见习招募34人模拟试卷及答案详解(网校专用)
- 2025江西南昌市东方航空配餐有限公司招聘劳务派遣人员1人模拟试卷及答案详解(名师系列)
- 2025河南郑州市第六人民医院招聘考前自测高频考点模拟试题及答案详解(全优)
- 2025年山东职业学院公开招聘人员(28名)模拟试卷及答案详解(必刷)
- 2025黑龙江五大连池风景区宣传和统一战线工作部招聘1名公益性岗位1人考前自测高频考点模拟试题(含答案详解)
- 2025福建三明市大田县住房和城乡建设局(房地产服务中心)补招聘工作人员(政府购买服务)1人模拟试卷及答案详解(有一套)
- 2025年牡丹江市高校毕业生留牡来牡就业创业专项行动工作的模拟试卷(含答案详解)
- 2025年上海新型烟草制品研究院有限公司所属企业招聘2人(第一批次)笔试题库历年考点版附带答案详解
- 铜仁市大学生乡村医生专项计划招聘考试真题
- 土地综合整治投标方案(技术方案)
- JJF(皖) 174-2024 重点用能单位能源资源计量在线审查规范
- JGJ-T+141-2017通风管道技术规程
- 历年全国《宪法》知识竞赛试题库完整版及答案【历年真题】
- 基本乐理(师范教育专业)全套教学课件
- 医院检验科实验室生物安全程序文件SOP
- JJG 270-2008血压计和血压表
- 《解剖学基础》课件-上肢骨及其连接
- 轻质燃料油安全技术说明书样本
- 小米全屋智能方案
评论
0/150
提交评论