1.背景
对于一般性的地图显示需求,我们只需要知道地图的一个固定URL,然后知道要显示的范围和要显示的级别以及每个级别的scale等即可。
但是如果我们遇到下面几种情况时,又该如何。
(1)需要显示的图层的不同级别来自于不同的服务器(URL不同):如前几个级别由一个固定URL提供,后几个级别由另外一个URL提供。
(2)需要同时叠加几张图层:如需要叠加地形图和注记图,且图层的显示有层级之分。
(3)需要同时叠加几张图层,并且每个图层初始显示级别不一样。
(4)需要同时叠加几张图层,图层的URL的请求方式均有不同:如其中一个图层由AGS提供,一个图层是天地图服务提供,还有一个图层是当地城管局提供,最后还有一个图层是由本地未发布的缓存瓦片提供。
诸如类似于以上的地图显示需求还有很多,如果我们单纯的对每一种情况进行一个代码上的分支当然也是能解决的。但是这并不是最好的一种解决方法,每次新的需求提出时,都需要对代码修改,这不符合设计模式中的开放封闭原则。虽然我们可以用简单工厂等模式来努力改善这种情况,不过如果有更好的方式,不需要修改任何代码,不需要用设计模式,就能解决以上的问题是否更好?
下面我将给出一种通过
数据库配置来实现上面所有问题的解决方案。此方案在多个项目中已经开始使用。
2.配置(表)的设计
2.1原理
首先我们必须对以上多种地图显示的需求进行一个分析,提出他们的共同点。
(1)对图层开始显示的级别有需求(startLevel)。
(2)多张图层叠加,并且图层叠加有从上自下的顺序(layerDisplayOrder)。
(3)图层可能的URL不同(ServiceURL)
(4)每个图层的URL格式可以不一样,比如URL可能天地图的WMTS格式,可能是AGS发布的请求方式(level\row\col),也有可能是Geoserver发布的WMS格式。并且有的服务提供商还需要token字段来判断是否有权限得到服务,或者不同的服务商提供的WMTS格式中对col和row以及level的表述字段名称不一样(通过这个分析,可以提炼出Xfield、Yfield、LevelFieldName、Token字段来对不同的需求进行配置)。
(5)所有的图层,如果要叠加,需要用同一个空间参考,同一个瓦片大小,同一个地图起始原点,以及同一套地图比例尺。
(6)变化的只是URL,其核心瓦片行列号和地图级别是每种瓦片请求URL均需要的。
2.2设计
2.2.1图层列表(tcMaplayerList)的设计
首先我给出图层列表设计的截图:
(1)每一个图层均有一个图层名
(2)每一个图层均有自己的图层类型,比如AGS类型的、WMTS类型的等。这是为了程序中对不同的类型的URL进行解析。
(3)每一个图层有其自己的显示顺序,为了正确的叠加图层之用。
(4)每一个图层也有自己开始显示的级别。如有的图层想第一级别开始显示,有的图层希望地图放大到第二级别时才开始显示。
2.2.2图层详细内容列表(tcgismapservicedetail)的设计
同样,这里先给出表的截图:
(1)ItemID为每个记录的主码。
(2)layerName与tcMaplayerList中的图层名是对应的。
(3)图层级别表示的是该图层在此级别时的信息。
(4)图层在该级别的URL有一个固定的部分,比如WMTS请求中,变化的只是行列号,而前面的部分均是不定的。
(5)Token、XFieldName、YFieldName、LevelFieldName均是为扩展之用,当URL需要给行列号以及级别一个固定的名称时等,则配置。否则不用。
3.工作流程
3.1流程图
3.2流程详解
3.2.1得到需要显示的图层列表
在显示地图之前,需要先向后台发出请求,此请求的参数主要是layerType,后台根据layerType将tcMapLayerList表中符合需求的内容读出返回。
3.2.2 解析当前地图级别下的各个图层信息,并顺序显示
在解析了需要显示的图层列表后,每个图层均会给后台发出自己的信息,信息中包括了此时地图的级别,以及需要得到的瓦片行列号和自己的图层名。
但是此时的地图级别并不是图层发给后台的级别参数,真是的地图级别是:
factLayerLevel=layerLevel+startLayerLevel。
根据factlayerLevel和layerName,在tcgismapservicedetail中找到对应的信息,如果有信息,则表示该图层在此真实级别下是需要显示的,然后根据该图层的layerType将此时的URL拼接出来,下载瓦片然后返回此瓦片。
如果没有查到数据则不显示此地图级别下的该图层。
4.成果展示
4.1测绘局地图+本地瓦片
4.2 天地图地形图层+天地图注记图层+Geoserver发布的行政区划图层
4.3测绘局提供的管线WMTS图层+本地AGS发布的地形图层
5.总结
通过此配置基本可以实现项目中遇到的绝大部分地图需求。改进后,虽然不再需要对各种需求进行大规模的代码编写,但是针对不同的瓦片类型的URL获取依然是要走分支的,这里可以通过策略模式来让代码更加规范。
断了一个多月没写博,WebGIS的原理系列会继续写下去的,希望大家持续关注。
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
很久以前就看到了Burp suite这个工具了,当时感觉好NB,但全英文的用起来很是蛋疼,网上也没找到什么教程,就把这事给忘了。今天准备开始好好
学习这个渗透神器,也正好给大家分享下。(注:内容大部分是
百度的,我只是分享下自已的学习过程)
Burp Suite 是用于攻击
web 应用程序的集成平台。它包含了许多工具,并为这些工具设计了许多接口,以促进加快攻击应用程序的过程。所有的工具都共享一个能处理并显示HTTP 消息,持久性,认证,代理,日志,警报的一个强大的可扩展的框架。
Burp Suite 能高效率地与单个工具一起工作,例如: 一个中心站点地图是用于汇总收集到的目标应用程序信息,并通过确定的范围来指导单个程序工作。
在一个工具处理HTTP 请求和响应时,它可以选择调用其他任意的Burp工具。例如:
代理记录的请求可被Intruder 用来构造一个自定义的自动攻击的准则,也可被Repeater 用来手动攻击,也可被Scanner 用来分析漏洞,或者被Spider(网络爬虫)用来自动搜索内容。应用程序可以是“被动地”运行,而不是产生大量的自动请求。Burp Proxy 把所有通过的请求和响应解析为连接和形式,同时站点地图也相应地更新。由于完全的控制了每一个请求,你就可以以一种非入侵的方式来探测敏感的应用程序。
当你浏览网页(这取决于定义的目标范围)时,通过自动扫描经过代理的请求就能发现安全漏洞。
IburpExtender 是用来扩展Burp Suite 和单个工具的功能。一个工具处理的数据结果,可以被其他工具随意的使用,并产生相应的结果。
BurpSuite工具箱
Proxy——是一个拦截HTTP/S的代理服务器,作为一个在浏览器和目标应用程序之间的中间人,允许你拦截,查看,修改在两个方向上的原始数据流。
Spider——是一个应用智能感应的网络爬虫,它能完整的枚举应用程序的内容和功能。
Scanner[仅限专业版]——是一个高级的工具,执行后,它能自动地发现web 应用程序的安全漏洞。
Intruder——是一个定制的高度可配置的工具,对web应用程序进行自动化攻击,如:枚举标识符,收集有用的数据,以及使用fuzzing 技术探测常规漏洞。
Repeater——是一个靠手动操作来补发单独的HTTP 请求,并分析应用程序响应的工具。
Sequencer——是一个用来分析那些不可预知的应用程序会话令牌和重要数据项的随机性的工具。
Decoder——是一个进行手动执行或对应用程序数据者智能解码编码的工具。
Comparer——是一个实用的工具,通常是通过一些相关的请求和响应得到两项数据的一个可视化的“差异”。
BurpSuite的使用
当Burp Suite 运行后,Burp Proxy 开起默认的8080 端口作为本地代理接口。通过置一个web 浏览器使用其代理服务器,所有的网站流量可以被拦截,查看和修改。默认情况下,对非媒体资源的请求将被拦截并显示(可以通过Burp Proxy 选项里的options 选项修改默认值)。对所有通过Burp Proxy 网站流量使用预设的方案进行分析,然后纳入到目标站点地图中,来勾勒出一张包含访问的应用程序的内容和功能的画面。在Burp Suite 专业版中,默认情况下,Burp Scanner是被动地分析所有的请求来确定一系列的安全漏洞。
在你开始认真的工作之前,你最好为指定工作范围。最简单的方法就是浏览访问目标应用程序,然后找到相关主机或目录的站点地图,并使用上下菜单添加URL 路径范围。通过配置的这个中心范围,能以任意方式控制单个Burp 工具的运行。
当你浏览目标应用程序时,你可以手动编辑代理截获的请求和响应,或者把拦截完全关闭。在拦截关闭后,每一个请求,响应和内容的历史记录仍能再站点地图中积累下来。
和修改代理内截获的消息一样,你可以把这些消息发送到其他Burp 工具执行一些操作:
你可以把请求发送到Repeater,手动微调这些对应用程序的攻击,并重新发送多次的单独请求。
[专业版]你可以把请求发送到Scanner,执行主动或被动的漏洞扫描。
你可以把请求发送到Intruer,加载一个自定义的自动攻击方案,进行确定一些常规漏洞。
如果你看到一个响应,包含不可预知内容的会话令牌或其他标识符,你可以把它发送到Sequencer 来
测试它的随机性。
当请求或响应中包含不透明数据时,可以把它发送到Decoder 进行智能解码和识别一些隐藏的信息。
[专业版]你可使用一些engagement 工具使你的工作更快更有效。
你在代理历史记录的项目,单个主机,站点地图里目录和文件,或者请求响应上显示可以使用工具的任意地方上执行任意以上的操作。
可以通过一个中央日志记录的功能,来记录所单个工具或整个套件发出的请求和响应。
这些工具可以运行在一个单一的选项卡窗口或者一个被分离的单个窗口。所有的工具和套件的配置信息是可选为通过程序持久性的加载。在Burp Suite 专业版中,你可以保存整个组件工具的设置状态,在下次加载过来恢复你的工具。
burpsuite专业版的个人感受
不知不觉使用burpsuite也有点年头了。它在我日常进行安全评估,它已经变得日益重要。
现在已经变成我在日常渗透测试中不可缺少的工具之一。burpsuite官方现在已经更新到1.5,与之前的一点1.4相比。界面做了比较大的变化。而且还增加了自定义快捷键功能。burpsuite1.5缺点是对中文字符一如既往的乱码。burpsuite入门的难点是:入门很难,参数复杂,但是一旦掌握它的使用方法,在日常工作中肯定会如虎添翼。
网上有破解版的下载,请自行百度。
一 快速入门(Burpsuite的安装使用与改包上传)
BlAck.Eagle
Burp suite 是一个
安全测试框架,它整合了很多的安全工具,对于渗透的朋友来说,是不可多得的一款囊中工具包,今天笔者带领大家来解读如何通过该工具迅速处理“截断上传”的漏洞。如果对该漏洞还不熟悉,就该读下以前的黑客X档案补习下了。
由于该工具是通过
Java写的,所以需要安装 JDK,关于 JDK 的安装,笔者简单介绍 下。基本是傻瓜化安装,安装完成后需要简单配置下环境变量,右键 ”我的电脑”->”属 性”->”高级”->”环境变量”,在系统变量中查找 Path,然后点击编辑,把 JDK 的 bin 目 录写在 path 的最后即可。如图 1
图 1
通过 CMD 执行下 javac 看是否成功安装。安装配置成功则会显示下图信息。如图 2
图 2 这里的测试网址是笔者帮朋友测试某站点时获取的,登陆后台后可以发表新闻,但是只能上 传图片。然后发现新闻的图片目录在 upload/newsimg 下。如图 3
图 3
后台有个” 网站资料设置”的功能,可以自行定义新闻图片的路径,但是当尝试把新闻图片
路径改为”upload/newsimg.asp/”时,上传图片竟然上传失败了,所以这种方法失效。 如图 4、
图 4
接下来我们尝试上传截断漏洞,可能大家经常用的是通过 WSockExpert 抓包,修改后通过 nc 来提交获取 shell,但是大家会发现那样有点繁琐,我们看看今天的方法是多么高效: 首先允许 burpsuite.jar,然后点击”proxy” 标签,会发现 burp suite 默认的代理监听端 口为 8080,如果怕跟自己电脑上的某个端口冲突的话,可以点击”edit”进行编辑。笔者 使用默认端口。如图 5
图 5
然后打开本地浏览器的代理设置的地方,IE 一般为”工具”->”Internet 选项” –>”连 接”->”局域网设置”,其他浏览器大致相同。如图 6
图 6
设置代理之后,我们到后台的某个上传新闻图片的地方上传一个图片木马,我这里的图片木 马为asp 一句话。如图 7
图 7
点击上传之后,我们到 proxy 的”history”选项,会发现该网站的某个 POST 提交请求, 我们选中该链接后,右键选择”send to repeater”,如图 8
图 8
我们会发现刚才提交的请求的数据包,那么我们看一下,有一项是上传路径的地方,我们以
前 进 行 截 断 上 传 的 时 候 经 常 修 改 这 个 地 方 , 这 次 也 不 例 外 , 我 们 把 上 传 路 径 改 为”upload/shell.asp ”后面有一个空格,如图 9
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
1.测试对象
这次测了一些http接口和几个网页。
2.测试策略
2.1 基准
测试:单个调用各接口循环100次计算平均响应时间
2.2
性能测试:单个接口调用以50并发用户数为单位,逐步加压直到预估的实际负载300并发用户,观察测试指标变化
2.3
压力测试:单个接口调用以50并发用户数为单位,逐步加压直到错误率过高或服务器资源使用率过高,观察测试指标变化
2.4 负载测试:预估实际负载为300并发用户数,在此基础上持续测试5分钟左右,观察测试指标是否达标
2.5 稳定性测试:预估实际负载为300并发用户数,在此基础上持续测试60分钟左右,观察测试指标是否达标,重点观察错误率
2.6 疲劳性测试:预估实际负载为300并发用户数,在此基础上持续测试240分钟左右,观察测试指标是否达标,重点观察错误率
2.7 组合测试:对2.2-2.5的测试采用不同接口同时调用(即系统不同模块同时测试)
2.8 其他:以不同ip地址加压,测试服务器负载均衡效果。
以上,本次只做了2.2、2.3、2.4、2.8
3.测试指标
测响应时间、错误率;同时专人监控服务器硬件资源使用状况、监控tomcat应用服务器等。
计算和监控吞吐量(测试工具自动计算测试执行过程中的吞吐量(每秒钟处理请求数),同时服务器监控软件业监控到了测试执行时服务器的吞吐量)
本次实际测试得到吞吐量距离预估有较大差距;错误率超出预期;且测试数据准备有一定问题。
4.测试工具
需设置语言为英文,默认中文翻译不完整。
5.测试脚本编写、调试
5.1 提前对接口、网页进行录制。每个待测接口、网页需要加断言。 断言多采用JQuery断言和Regular Expression断言
5.2 重点在测试数据的准备。
5.3 采用了本地
web应用提供数据,jmeter获取这些数据,再发送给服务器的方法(这次发现这个本地应用生成的数据在较高并发时有重复,导致了不必要的错误率)
5.4 测试结果监听器: assertion results, summary report, aggregate report, result tree, result table
5.5 测试接口调用时,可用网页、
数据库等其他方法确认接口调用成功。观察接口调用是否生效,是否和网页同样效果。
6.测试执行
6.1 一台电脑加压300-600并发用户。如果需要更多则需要增加电脑。
6.2 以不同ip地址加压,测试服务器负载均衡效果。
6.3 机房测试,排除internet网络延迟问题
6.4 数据备份和还原,排除性能测试对数据的改变
6.5 生产环境测试(系统未上线),排除测试环境的影响
7.测试报告
7.1 截取了jmeter监听器的结果,可以截取服务器监控的截图
8.调优
本次测试结果不理想,服务器因硬件强大,几乎无负载,但应用本身有
java出错。并发现接口调用结果未正确影响网页的bug。
后续需要等开发修复、优化之后再次测试
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
一、什么是 ARC ?
所谓ARC就是Automatic Reference Counting , 即自动引用计数。ARC是自iOS5引入的。ARC机制的引入是为了简化开发过程的内存管理的。相对于之前的MRC (Manual Reference Counting) , ARC机制显得更加自动化。在使用ARC开发过程中,开发者只需考虑strong / weak 的使用,不再需要考虑对象何时要retain,release/autorealease。使用ARC一般不会降低程序的效率。
ARC一个很重要的原则是:只要某个对象被任一strong指针指向,那么它将不会被销毁。如果对象没有被任何strong指针指向,那么就将被销毁。
ARC是基于引用计数的,当某个对象被一个strong指针指向时,它的计数+1。当没有strong指针指向时,其计数为0,此时对象会被销毁。只要一个对象有至少一个strong指针指向时,它就不会被销毁。但ARC容易造成一个 Strong Reference Cycle 的问题,这样即使AddBook 和 Entry 这两个对象都不再使用了,但是由于ARC机制,这两个对象都互相有strong指针指向,所以这两个对象都不会被回收,从而造成内存无法被释放。
针对上面的情况,有一种解决方法:在其中一个对象中引入weak,替换其strong
引入weak后,当entry使用完后,由于指向AddrBook没有strong指针,所以AddrBook会首先被释放,然后由于AddrBook被释放,指向Entry的Strong指针也会销毁,此时没有指向Entry的strong指针,所以Entry也会被释放。这样就不会出现内存无法被释放的情况。
这里就有一个问题了,什么时候应该用strong,什么时候应该用weak呢?看以下解析:
如图所示,ViewController直接持有View,所以ViewController应该要有一个strong指向view。同理,view直接持有subviews,所以也应该要有strong指向subviews。由于viewcontroller要使用subviews对象,但又不想直接持有subviews,所以只好通过weak指向subviews。这样的话,可以在viewcontroller中不改变view的持有关系,就可以使用subviews对象。从图中可以得出一个通用的规律:对于有直接持有的关系,持有者要通过strong指向被持有者。对于有间接持有关系的,间接持有者需通过weak指向间接被持有者。
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
Java远程方法调用,即Java RMI(Java Remote Method Invocation)是Java编程语言里,一种用于实现远程过程调用的应用程序编程接口。它使客户机上运行的程序可以调用远程服务器上的对象。远程方法调用特性使Java编程人员能够在网络环境中分布操作。RMI全部的宗旨就是尽可能简化远程接口对象的使用。
一、创建RMI程序的4个步骤
1、定义一个远程接口的接口,该接口中的每一个方法必须声明它将产生一个RemoteException异常。
2、定义一个实现该接口的类。
3、创建一个服务,用于发布2中定义的类。
4、创建一个客户程序进行RMI调用。
二、程序的详细实现
1.首先我们先创建一个实体类,这个类需要实现Serializable接口,用于信息的传输。
1 import java.io.Serializable; 3 public class Student implements Serializable { 5 private String name; 7 private int age; 9 public String getName() { 11 return name; 13 } 15 public void setName(String name) { 17 this.name = name; 19 } 21 public int getAge() { 23 return age; 25 } 27 public void setAge(int age) { 29 this.age = age; 31 } 33 } |
2.定义一个接口,这个接口需要继承Remote接口,这个接口中的方法必须声明RemoteException异常。
1 import java.rmi.Remote;
3 import java.rmi.RemoteException;
5 import java.util.List;
6 public interface StudentService extends Remote {
12 List<Student> getList() throws RemoteException;
14 }
3.创建一个类,并实现步骤2中的接口,但还需要继承UnicastRemoteObject类和显示写出无参的构造函数。
1 import java.rmi.RemoteException; 3 import java.rmi.server.UnicastRemoteObject; 5 import java.util.ArrayList; 7 import java.util.List; 11 public class StudentServiceImpl extends UnicastRemoteObject implements 13 StudentService { 15 public StudentServiceImpl() throws RemoteException { 17 } 21 public List<Student> getList() throws RemoteException { 23 List<Student> list=new ArrayList<Student>(); 25 Student s1=new Student(); 27 s1.setName("张三"); 29 s1.setAge(15); 31 Student s2=new Student(); 33 s2.setName("李四"); 35 s2.setAge(20); 37 list.add(s1); 39 list.add(s2); 41 return list; 43 } 45 } |
4.创建服务并启动服务
1 import java.rmi.Naming; 2 import java.rmi.registry.LocateRegistry; 4 public class SetService { 6 public static void main(String[] args) { 8 try { 10 StudentService studentService=new StudentServiceImpl(); 12 LocateRegistry.createRegistry(5008);//定义端口号 14 Naming.rebind("rmi://127.0.0.1:5008/StudentService", studentService); 16 System.out.println("服务已启动"); 18 } catch (Exception e) { 20 e.printStackTrace(); 22 } 24 } 26 } |
5. 创建一个客户程序进行RMI调用。
1 import java.rmi.Naming; 3 import java.util.List; 5 public class GetService { 9 public static void main(String[] args) { 11 try { 13 StudentService studentService=(StudentService) Naming.lookup("rmi://127.0.0.1:5008/StudentService"); 15 List<Student> list = studentService.getList(); 17 for (Student s : list) { 19 System.out.println("姓名:"+s.getName()+",年龄:"+s.getAge()); 21 } 23 } catch (Exception e) { 25 e.printStackTrace(); 27 } 29 } 33 } |
6.控制台显示结果
=============控制台============
姓名:张三,年龄:15
姓名:李四,年龄:20
===============================
在Spring中配置Rmi服务
将Rmi和Spring结合起来用的话,比上面实现Rmi服务要方便的多。
1.首先我们定义接口,此时定义的接口不需要继承其他接口,只是一个普通的接口
1 package service;
3 import java.util.List;
5 public interface StudentService {
7 List<Student> getList();
9 }
2.定义一个类,实现这个接口,这个类也只需实现步骤一定义的接口,不需要额外的操作
1 package service; 4 import java.util.ArrayList; 6 import java.util.List; 9 public class StudentServiceImpl implements StudentService { 11 public List<Student> getList() { 13 List<Student> list=new ArrayList<Student>(); 15 Student s1=new Student(); 17 s1.setName("张三"); 19 s1.setAge(15); 21 Student s2=new Student(); 23 s2.setName("李四"); 25 s2.setAge(20); 27 list.add(s1); 29 list.add(s2); 31 return list; 33 } 35 } |
3.接一下来在applicationContext.xml配置需要的信息
a.首先定义服务bean
<bean id="studentService" class="service.StudentServiceImpl"></bean>
b.定义导出服务
<bean class="org.springframework.remoting.rmi.RmiServiceExporter"
p:service-ref="studentService"
p:serviceInterface="service.StudentService"
p:serviceName="StudentService"
p:registryPort="5008"
/>
也可以增加p:registryHost属性设置主机
c.在客户端的applicationContext.xml中定义得到服务的bean(这里的例子是把导出服务bean和客户端的bean放在一个applicationContext.xml中的)
<bean id="getStudentService"
class="org.springframework.remoting.rmi.RmiProxyFactoryBean"
p:serviceUrl="rmi://127.0.0.1:5008/StudentService"
p:serviceInterface="service.StudentService"
/>
d.配置的东西就这么多,是不是比上面的现实要方便的多呀!现在我们来测试一下
1 package service; 2 import java.util.List; 3 import org.springframework.context.ApplicationContext; 4 import org.springframework.context.support.ClassPathXmlApplicationContext; 5 public class Test { 6 public static void main(String[] args) { 7 ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml"); 8 StudentService studentService=(StudentService) ctx.getBean("getStudentService"); 9 List<Student> list = studentService.getList(); 10 for (Student s : list) { 11 System.out.println("姓名:"+s.getName()+",年龄:"+s.getAge()); 12 } 13 } 14 } |
=============控制台============
姓名:张三,年龄:15
姓名:李四,年龄:20
=============================
上面的mian方法运行可能会报错,应该是spring的jar少了,自己注意添加。
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
关于<<提高代码质量系列>>
这是我新开的一个系列,旨在记录我对整个编码规范,代码风格,语法习惯,架构设计的一些思考,感悟和总结.
前言
不知道大家会不会觉得我的标题很噱头,不是一般应该提倡写注释的么?首先我得解释下,我这句话有两个意思!
1,绝非提倡不写注释,而是不要写不必要的注释.
2,命名规范的作用大于注释
好吧,这么一说,其实还是有点噱头的感觉的,因为我这篇文其实重心更放在强调命名规范和设计规范上面,良好的规范,让你的代码有自释性,省去了注释的步骤.
还要强调下的是:这个观点绝非我自己主观臆断,凭空瞎想出来的. 而是实实在在由项目开发里面总结出来的.
为什么我有这个想法呢?请继续看我的蛋疼经历.
正文
先上段奇葩代码
/// <summary> /// 根据产品ID获取产品列表 /// </summary> /// <param name="columnID">关键字</param> public DataTable GetColumnInfoByColumnID(int columnID) { return DALColumn.GetColumnInfoByColumnID(columnID); } /// <summary> /// 根据产品名称获取产品列表 /// </summary> /// <param name="columnID">关键字</param> public DataTable GetColumnInfoByColumnID(string columnName) { return DALColumn.GetColumnInfoByColumnID(columnName); } |
这是我现在维护的一个老项目了,经手的人比较多,代码写的比较垃圾,我们先不吐槽这种纯粹是脱裤子放屁的所谓三层和明明返回的是dataTable又扯什么info,就说这两个函数,tm功能应该是不一样的吧,为啥名字一模一样?这是在闹哪样?
其实,造成这种情况的原因,我们都知道,就是某类程序员的ctrl c+v大法,这两个函数底层逻辑比较相似,懒得重构,直接copy了改改多快!copy就copy吧,好歹名字改一下啊!也许这位前辈会说,我不是写了注释了吗,看到注释,不就知道这个函数是干嘛的了?但问题是,其他调用的人,首先看到的,肯定是函数名啊,GetColumnInfoByColumnID,多直观 通过id来查找呗.虽然这里有两个比较违和的地方,一是参数默认名字是columnName,二是参数类型不是int而是string,但反正这项目的代码不规范,如果调用的人也够粗心,那么,一个隐晦的bug,就这么产生了.
如果改下名字,叫 GetTableByColumnName,这种错误发生的概率无疑会减少很多了,
Ps:其实现在ide功能这么强大,只要你确定没有反射调用这个函数的地方,完全可以使用全局重命名的方法,一步到位.
看到这里读者朋友们可能会说,这是命名不规范嘛,和写不写注释有什么关系呢?
我们可以假想一种这样的情况,同样是拷贝代码,拷贝者改了函数名,这个名字语义清晰,表意清楚, 但他却忘记改注释了,结果函数是新的函数签名,函数的注释却是其他一段莫名其妙的注释.
或者再假想一种情况.
原来的一个函数,名字和注释是对应的,随便举个例子,叫GetTypeByColumnId吧,注释为"通过产品id取得产品类型"
一切ok,是吧! 但是现在逻辑变了,比如说Type和产品无关了,需要通过生产批次来确定,于是一个代码维护者,将函数重写了一下功能ok,满足新需求!!同时他比上个人好一点,他记得改函数名,然后他改为了GetTypeBySerialId,这名字也很ok,一切也都看起来很好.但偏偏他漏掉了注释,但代码仍然运行的很ok,毕竟注释可不受.net的元数据的支持,ide也不可能知道你这里犯了这么一个错误是吧!
然后,接下来的场景大家很容易就可以联想到,
如果是细心的调用者:
尼玛!函数注释让我传"产品id",但函数名和参数默认名字又是"SerialId"(流水号),这尼玛闹哪样?
如果是粗心的调用者呢?两种情况呗:
1.看了函数名字和默认参数名字 ,没注意到注释,ok,算这个粗心的小伙伴幸运,
2.看了注释,然后这小伙伴不认识SerialId这个单词的意思. 然后,一个悲伤的故事就这么发生了!!
其实我以上举例的一些函数,都是功能比较具体,业务比较单纯,也容易用几个单词描述.对于这些函数,我个人的意见是,完全不需要去写注释,
有以下几个原因:
1.浪费精力去写,
2.调用的人需要把一段话读两遍(函数名和注释)
3.写了还需要人去维护,(改了代码,得同步去改注释)
4.如果强类型的参数传递不匹配,ide或者resharper插件会马上指出你的错误,但如果注释和代码不匹配,则除了通过人力CodeReview,没有其他任何办法去找出这种错误.
其中尤其是4,完全就是埋在项目中的地雷,除非你踩到了,不然很难排查.
当然,如果是反之,逻辑复杂,甚至有调用的前置约束,那肯定该写注释还是得写了.
同时,这里还提一个观点,注释比代码更有价值,因为代码毕竟大部分还是在讲怎么做(how do),而注释是讲做什么(do what)?抽象程度更高,对于比较复杂的代码,如果有注释,调用者一般都会优先去阅读注释,而不是去阅读代码,所以:轻易不写注释,但如果你写了,请一定要对你的注释负责,它比代码更需要你的细心呵护!!
好吧,我会说这篇文最大的作用,其实是可以让我吐槽发泄一下么?
我感觉自己现在就是在这种地雷坑里,天天过着提醒吊胆的日子.
以上例子皆非我刻意编造,来源于工作中的真实经历,只是稍作修饰,隐去了和业务有关的信息.
根据回复的补充:
相信很多博友有这样的经历,这个函数好复杂呀!我必须写注释啊,不然一段时间之后,我自己都看不明白了,
有些时候是确实很复杂,但也有时候是设计的问题,这时候,写注释其实是一种逃避了.逃避你需要继续深入思考这个函数的业务和功能的设计是否合理.
其实我这标题还有第三个意思:
在尽可能少写注释的前提下,如果一个函数的注释仍然超过了2处,那么我认为这个函数的设计是有问题的.
这个函数是否太大,是否是反模式里面提到的万应灵或屠龙术?
所以有时候说的 "因为业务复杂,很难用几个单词去描述,所以很难命名",就是如此.
你的业务抽象粒度是否太大?导致一个业务模块承担了过多的业务.
你是否把几个流程放在了一个节点上?导致引入了额外的逻辑判断甚至是多余的逻辑分支.
如果是这样,你想用几个单词来描述这么复杂的逻辑,当然是不可能了.
这点我会在后续的重构系列里面谈谈我自己的理解.
补充下自己的理解.
我始终认为好的设计,大部分情况下,函数名就足以解释它的功能,如果你遇到了两三个单词不能解释函数功能的情况----说明你该分解函数了!
比如一个大函数,OutPutMetaData,输入是源数据路径,使用的模板 返回解析之后的元数据
流程大概是 采集数据->分析数据->匹配模板->生成MetaData
代码是大约1-2k行,如果写在一个函数里面,当然也似可以的,但你想用注释解释清楚,必须在每个流程的关键节点写注释,遇到数据有前后关联关系的,还得思维反复跳来跳去.
但如果写成下面这种模式的代码,基本就像是阅读英文说明(注释),一样阅读代码了
public MetaData OutPutMetaData(string sourcePath, MetaTemplate template) { var metaDataFactory = new MetaDataFactory(); if (!metaDataFactory.CheckInput(sourcePath)) { throw new ErrorSourceException(); } if (!metaDataFactory.TransformSourceData(template)) { throw new ErrorFormatException(); } return metaDataFactory.CreateMetaData(); } |
这样写虽然多了很多的类和方法,还要额外定义一些中间数据的实体类型和自定义异常,但是经过合理的封装和命名之后,整个结构非常清晰,定位错误和修改流程也方便.
代码阅读速度基本和描述语句(注释)的阅读速度相当, 这就是代码即注释.
最后总结:
其实我认为最关键是要形成自己的编码规范,这个"规范"不仅仅指的是狭义的命名规则和代码格式,缩进,文件组织结构等.更关键的是,要形成一套有逻辑性,能自洽,有良性导向的一套思维模式,并时刻坚持遵守它,思考它,改进它.
这套思维模式你可以自由的去扩展,只要不偏离它的中心思想.比如我给自己扩展的一些要求:
1.函数一律使用动宾结构,如InitFactory,而不用FactoryInit,其实这两者没什么优劣,仅仅只是让自己习惯,以后思考和阅读自己代码的时候,能更快的带入过去的自己的思维,更快的理解自己的代码,同时找api也能节约一点时间.
2.html标签样式id小写开头,class大写开头,同理,其实也没啥原因,就是个习惯.
3.描述一个事物的时候要区分是what it is(名词,形容词)还是what it can do(动词,动名词,动宾短语)比如doClose, 是某个事物的动作,closing,和 closed则代表它的状态
再就是结构设计上的一些感觉了,这个比较抽象,不好用文字很准确的描述,大致意思就是我会从一些纬度对功能进行切分,access business viewModel show interaction 等,不一定都能分的非常清楚,也不强求一个区分度很高的边际,但至少要有个模糊的定位.
如果能长期坚持下来,以后阅读自己的代码是非常容易的,即使是没有(少量)注释.
Ps:锤炼自己的这套思维模式的方法也很简单,也就三点
多看优秀代码,自己动手多写,多思考总结.
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
一、Webbench简单介绍
在一个网站上线前, 通常我们应该做一些相关的
压力测试, 以便了解当前
Web服务器在高并发高负载情况下的响应状况和速度,方便对Web服务器进行优化和重构。目前有很多免费的web压力测试工具可以帮助我们完成测试, 例如: 十个免费的Web压力测试工具http://coolshell.cn/articles/2589.html,但在真实项目中使用Apache ab和Webbench来完成压力测试。Apache的优点:Apache的ab使用非常简单, 而且只要是安装了Apache了,就会自带其ab工具,缺点:就是不能模拟高并发状态下的测试, 好像最多可以模拟100-200次/秒的并发. 如果需要模拟更高负载的压力测试, 就需要使用Webbench。
Webbench是有名的网站
压力测试工具,它是由 Lionbridge公司(http://www.lionbridge.com)开发。Webbech能测试处在相同硬件上,不同服务的性能以及不同硬件上同一个服务的运行状况。webBech的标准测试可以向我们展示服务器的两项 内容:每秒钟相应请求数和每秒钟传输数据量。webbench不但能具有便准静态页面的测试能力,还能对动态页面(ASP,PHP,JAVA,CGI)进行测试的能力。还有就是他支持对含有SSL的安全网站例如电子商务网站进行静态或动态的
性能测试,webbench最多可以模拟3万个并发连接去测试网站的负载能力。缺点测试的结果太简单了。
二、安装Webbench
注意点:为了测试准确,请将 webbench 安装在别的linux服务器上,(因为webbench 做压力测试时,自身也会消耗CPU和内存资源, 否则很可能把自己服务器搞挂掉)
目前Webbench最新的版本为webbench-1.5.tar.gz下载地址 http://home.tiscali.cz/~cz210552/distfiles/webbench-1.5.tar.gz
1.先安装依赖包:yum install ctags
2.安装Webbench:
tar zxvfwebbench-1.5.tar.gz
cd webbench-1.5
make &&make install
如果出现以下报错信息:
ctags *.c /bin/sh: ctags: command not found make: [tags] Error 127 (ignored) install -s webbench /usr/local/bin install -m 644 webbench.1 /usr/local/man/man1 install: cannot create regular file `/usr/local/man/man1': No such file ordirectory make: *** [install] Error 1 |
解决方法:
mkdir -p /usr/local/man
chmod 644 /usr/local/man
再次执行make && make install
看到如下界面,说明安装成功
make: Nothing to be done for `all'. install -s webbench /usr/local/bin install -m 644 webbench.1/usr/local/man/man1 install -d /usr/local/share/doc/webbench install -m 644 debian/copyright/usr/local/share/doc/webbench install -m 644 debian/changelog/usr/local/share/doc/webbench |
三、使用
[root@centos ~]# webbench -c 400 -t 20 http://10.43.2.192/ Webbench - Simple Web Benchmark 1.5 Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software. Benchmarking: GET http://10.43.2.192/ 400 clients, running 20 sec. Speed=392676 pages/min, 1603427 bytes/sec. Requests: 130892 susceed, 0 failed. |
参数说明:-c表示并发数,-t表示时间(秒)
每秒钟传输数据量:1603427 bytes/sec每秒钟相应请求数:392676/60= 6544 pages/sec
这里有一个特别要注意的点:10.43.2.192/后面的“/”一定不要忘记
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters