代码规范

代码规范
这个词困扰了我多年
要统一代码规范, 我目前还没见过成功的例子,
各个公司, 各种语言, 甚至每个人都有自己的风格, 没有形成网格的也有习惯, 用别人的总是不那么顺手

常见的代码规范:
类的命名首字母大写, 然后驼峰命名
函数取名, 第一个词小写, 动词开头之类的
这2点还算厚道

然后
内部变量来了
以前学习C语言的喜欢 _a _i, lcName, lcCount, 之类

{}
C#的
if()
{
}

java的
if(){
}

运算符前后加个空格
i=1;
i = 1;

for(int i=0;i<len;i++)
for(int i=0; i<len; i++)


bool变量 一定要isExist, 之类的

大部代码规范文档都是这么要求的, 当然也是有它的道理和作用, 但执行起来很困难, 几乎全靠程序员自觉, 但这个人性, 你懂的, 要靠term leader吗? term leader也没那个功夫天天看, 行行看, 抽查的意义不大

而且这让很多编码人员很烦躁, 潜意识里被束缚着
所以, 多年来我一直没有去理会过代码规范这事

最近, 突然间有点觉悟, 是在写注释, 文档, 看代码时的一种突然间的顿悟
我不需要也不想读这么多代码,
80%的情况下, 我希望读少量注释或代码就能知道个大概就行
20%的情况下, 我希望针对小段代码深度分析
甚至有时候, 我希望针对一个方法, 直接进行替换而不是修改

为什么这么多年来都发现呢,
国内的编码环境, 追求快速编码, 快速实现功能
无视需求分析, 无视系统设计, 更别说写注释, 文档了
由程序员什么都没搞清就开始敲代码, 做设计, 那个不叫需求分析和系统设计

好了, 具体的代码规范: 不应该着眼于代码的命名, 变量的首字母之类的细枝末节
而是应该[b]体现在思路上, 算法, 数构结构上, 让代码容易读起来[/b]
或者说几行重点代码就能知道个大概了

让人在读代码的时候, 很清淅的知道你在干什么, 或者是你想干什么
典型的场景1: 自己读自己以前的代码的时候, 由于是自己写的, 代码是看懂了, 但却不知道为什么要这么写, 一改, 结果bug出来了

典型的场景2: 自己读自己以前的代码, 搞不清楚类级别的变量是干什么用的, 前后跨几屏的翻代码, 穿插几个函数终于弄清该变量是干什么的了, 然后大骂, 虽然那是你自己亲手造成的

典型的场景3: 自己写的一个类, 要跑单元测试了, 要么就是数据库错误, 要么就是提示缺少组件, 要么就是null异常, 前后翻几个类几个包, 把mysql跑起来, 把tomcat跑起来, 然后才能把这段代码跑起来, 其实我只是想测刚才修改了报表里的一个字段是否正确而已...


以下是我个人整理的代码规范

一 注释
1.类的注释
每个类都要有注释说明, 包括: 这个类的标题, 这个类是干什么用的, 结合使用场景的说, 这个类的使用有什么特殊情况, 这个类的调用说明

2.函数的注释
所有公共函数都强制需要注释, 这个方法是干什么用的, 结合使用场景的说, 这个类的使用有什么特殊情况, 调用方法说明, 每个参数, 能给个例子就给个例子, 如param path, 别人调用时传个"d:/abc/"出错了半天, 才发现原来是要这么传"d:/abc", 你还在那振振有词的说, 你的入参不合法

3.域级变量的注释
所有域级变量都强制需要注释, 这个变量是干什么用的, 结合使用场景的说, 这个变量的使用有什么特殊情况

4.数据结构的注释
重要的数据结构前添加注释, 样例

//转算为M M=1024byte*1024Kb
double d1=result[1]/((double)1024*1024);
//转算为G G=1024byte*1024Kb*1024Mb
double d2=(double)result[1]/(1024*1024*1024);

针对d1, d2, 与其在命名写 sizeOfMb, sizeOfGb, 还不如直接标个注释更清晰

5.注释越多越好
曾经在合资企业呆过一阵子, 用英文写过注释, 自己看起来还可以,
但问题是: 你能保证你的英文不写错, 却不能保证别人的英文不读错, 这里还有语言背景问题, 所以, 作为中国人, 还是别装B了, 有条件的话, 老老实实母语吧, 这样才能有最详细最有质量的备注,


二 类的设计
1. 类的定义
这个类是干什么的, 要在设计是就定义好, 要杜绝客串其他功能, 经常有"临时"需要添加的方法, 几个临时下来, 这个类的就偏离它的定义了, 事后你老人家一不小心想不起来, 那坑的就是自己, 你老人家拍拍屁股一走, 坑的就是接手的人, 对于"临时"函数, 要在类的注释里详细说明这个临时的缘由, 以及事后找时间重构(有条件的话)
2. 函数的定义
慎重使用公共方法, 每个公共函数都要有清淅的理由, 可public又可private的, 一律private, public是公开给外人使用的, 没事就别给别人找麻烦了, 再说了, 那个别人, 多半是你自己
3. 上下文
类和函数的上下文清淅, 要做到这点, 会付出一点代价: 性能浪费在上下文转化
但在95%的情况, 可以方便的整块替换函数比追求函数的运算效率更有意义

三 资源的生命周期
1. 资源的规划
规划好资源是内部生成还是依赖外面, 如connection, inputstream之类的, 内部资源要自己释放, 参见下面的 "2. 内部资源的释放" 和 "3. 外面资源的使用"

2. 内部资源的释放
在自己类里面生成的资源, 要自己释放, 系统后期大部分null, permSpace out of memory就是这么产生的, 虽说java有回收机制, 但你也不能完全靠它, 用完后写个obj=null相当于买一份保险, 例子: 对于foutinputstream fout之类的, 用完后一定要写个fout.close();而fout=null;则是建议写

3. 外面资源的使用
来自外面的资源, 用完就还, 别多手
典型案例:

public int setItem(conneciton, con, String sql, String[] param){
int flag=0;
try{
...
}catch(exception ex){
...
}finally{
//这里手一抖, 把con关掉了, 调用者就悲剧了
con.close();
con=null;
}
return flag;
}


可以看见, 代码规范其实保是系统设计(类设计)的衍生物, 有一个良好的系统设计(类设计), 才能用良好的代码规范


综合例子1

package zkhelper;

import java.io.File;
import java.util.LinkedList;
import java.util.List;

public class FileCounter {

long current;
long total;

List<IprocessAction> listener=new LinkedList<IprocessAction>();

public void checkFile(String name, File file) throws Exception{
try {
//long[] result=checkFileCnt(new File("d:/hyt/test60w"));
System.out.println("正在计算文件夹...");
long count_dir=checkFileCount(file);
total=count_dir;
current=0;
System.out.println("开始计算文件数量和容量...");
long[] result=checkFileSize1(file);
//转算为M M=1024byte*1024Kb
double d1=result[1]/((double)1024*1024);
//转算为G G=1024byte*1024Kb*1024Mb
double d2=(double)result[1]/(1024*1024*1024);
System.out.println(name);
System.out.println(String.format("容量: %.3f M", d1));
System.out.println(String.format("容量: %.3f G", d2));
System.out.println("文件数量:"+result[0]);
System.out.println(String.format("容量: %.3fG, 数量: %d", d2, result[0]));
} catch (Exception e) {
e.printStackTrace();
}
}

public static long checkFileCount(File file) throws Exception {
if(null==file)
throw new Exception(String.format("指定目录为null"));
if(!file.isDirectory())
throw new Exception(String.format("指定目录(%s)无效", file.getPath()));

long count=0;
File[] ff=file.listFiles();
for(File f : ff){
String name=f.getName().toLowerCase();
if(f.isDirectory()){
System.out.println(name);
count++;
long n=checkFileCount(f);
count+=n;
}
}
// System.out.println("file: "+file.getName());
// System.out.println(String.format("total file cnt: %d", cnt));
// System.out.println(String.format("total size: %d", amount));
return count;
}

public static long[] checkFileSize(File file) throws Exception {
if(null==file)
throw new Exception(String.format("指定目录为null"));
if(!file.isDirectory())
throw new Exception(String.format("指定目录(%s)无效", file.getPath()));

File[] ff=file.listFiles();
long count=0;
long size=0;
for(File f : ff){
String name=f.getName().toLowerCase();
if(f.isDirectory()){
long[] tmp=checkFileSize(f);
count+=tmp[0];
size+=tmp[1];
}else if(f.isFile()){
boolean flag=false;
if(name.lastIndexOf(".jpg")>=0 || name.lastIndexOf(".gif")>=0 || name.lastIndexOf(".tif")>=0)
flag=true;
if(!flag)
continue;
count++;
long d=f.length();
size+=d;
}
}
// System.out.println("file: "+file.getName());
// System.out.println(String.format("total file cnt: %d", cnt));
// System.out.println(String.format("total size: %d", amount));
long[] result={count, size};
return result;
}

public long[] checkFileSize1(File file) throws Exception {
if(null==file)
throw new Exception(String.format("指定目录为null"));
if(!file.isDirectory())
throw new Exception(String.format("指定目录(%s)无效", file.getPath()));

File[] ff=file.listFiles();
long count=0;
long size=0;
for(File f : ff){
String name=f.getName().toLowerCase();
if(f.isDirectory()){
current++;
String[] param={String.valueOf(current), String.valueOf(total), name};
notify(param);
long[] tmp=checkFileSize1(f);
count+=tmp[0];
size+=tmp[1];
}else if(f.isFile()){
boolean flag=false;
if(name.lastIndexOf(".jpg")>=0 || name.lastIndexOf(".gif")>=0 || name.lastIndexOf(".tif")>=0)
flag=true;
if(!flag)
continue;
count++;
long d=f.length();
size+=d;
}
}
// System.out.println("file: "+file.getName());
// System.out.println(String.format("total file cnt: %d", cnt));
// System.out.println(String.format("total size: %d", amount));
long[] result={count, size};
return result;
}

public void notify(String[] param){
for(IprocessAction e : listener){
e.actionPerom(param);
}
}

public void addEvent(IprocessAction e){
listener.add(e);
}

public void removeEvent(IprocessAction e){
listener.remove(e);
}

/**
* @param args the command line arguments
*/
public static void main(String[] args) throws Exception {
String name="网球图片";
File file=new File("E:\\picture\\[20120102]莲花二村");
FileCounter fc=new FileCounter();
fc.addEvent(new IprocessAction() {

@Override
public void actionPerom(String[] param) {
double n=100*new Double(param[0])/new Integer(param[1]);
System.out.println(String.format("current: %s, total: %s, process: %.0f%%, name: %s", param[0], param[1], n, param[2]));
}
});
fc.checkFile(name, file);
}

interface IprocessAction {

/**
* 进度事件
* @param param {current, total, desc}
*/
public void actionPerom(String[] param);
}
}


FileCounter这个类是我去年写的, 我只依稀记得这个类是计算文件数量和容量的, 如今我要使用这个类了, 看着这个类的导航,

[img]http://dl2.iteye.com/upload/attachment/0103/5123/4e28ee17-1e3d-3a3d-a4df-3676e35f3621.jpg[/img]

我面临以下问题

1.有动态函数和静态函数, 我要怎么使用呢, 这涉及到是否new FileCounter的问题, 这需要我去读代码
2.checkFileSize和checkFileSize1是怎么回事
3.checkFile, checkFileCount, checkFileSize, 看起来第一个函数包括了第2,3个函数的作用, 但我不敢肯定, 这需要我看一下checkFile的代码
4.公共域current和long是干什么用的

------------------------------------------------------------

这是添加注释之后的代码

package zkhelper;

import java.io.File;
import java.util.LinkedList;
import java.util.List;

/**
* 检查子图数量和容量的工具类<br />
* <br />
* 调用方法:<br />
* 1.FileCounter fc=new FileCounter()<br />
* 2.fc.addEvent(IprocessAction); 注册进度事件<br />
* 3.checkFile(name, file); 开始运算<br />
* 科目, 子图容量, 子图数量会显示在控制台<br />
* @author lizw
*/
public class FileCounter {

long current; //在checkFileSize用于递归算法, 记录当前是第几个目录
long total; //在checkFileSize用于递归算法, 记录一共有几个目录

List<IprocessAction> listener=new LinkedList<IprocessAction>();

/**
* 在控制台打印科目子图容量和数量
* @param name 科目别名
* @param file 科目子图路径, 如//192.168.1.200/d$/2014yczk/YCZK/jykm/jy_dl/jpg/Current/Image
* @throws Exception
*/
public void checkFile(String name, File file) throws Exception{
try {
//long[] result=checkFileCnt(new File("d:/hyt/test60w"));
System.out.println("正在计算文件夹...");
long count_dir=checkFileCount(file);
total=count_dir;
current=0;
System.out.println("开始计算文件数量和容量...");
long[] result=checkFileSize(file);
//转算为M M=1024byte*1024Kb
double d1=result[1]/((double)1024*1024);
//转算为G G=1024byte*1024Kb*1024Mb
double d2=(double)result[1]/(1024*1024*1024);
System.out.println(name);
System.out.println(String.format("容量: %.3f M", d1));
System.out.println(String.format("容量: %.3f G", d2));
System.out.println("文件数量:"+result[0]);
System.out.println(String.format("容量: %.3fG, 数量: %d", d2, result[0]));
} catch (Exception e) {
e.printStackTrace();
}
}

/**
* 计算目录数量, 为统计子图数量的进度提供总数
* @param file
* @return
* @throws Exception
*/
public long checkFileCount(File file) throws Exception {
if(null==file)
throw new Exception(String.format("指定目录为null"));
if(!file.isDirectory())
throw new Exception(String.format("指定目录(%s)无效", file.getPath()));

long count=0;
File[] ff=file.listFiles();
for(File f : ff){
String name=f.getName().toLowerCase();
if(f.isDirectory()){
System.out.println(name);
count++;
long n=checkFileCount(f);
count+=n;
}
}
// System.out.println("file: "+file.getName());
// System.out.println(String.format("total file cnt: %d", cnt));
// System.out.println(String.format("total size: %d", amount));
return count;
}

/**
* 由于添加了进度事件, 此方法静态转成动态函数
* 计算图像的数和大小<br />
* 图像大小返回的是byte单位, 默认只统计(jpg, gif, tif)<br />
* 使用了递归算法
* @param file 科目子图路径, 如//192.168.1.200/d$/2014yczk/YCZK/jykm/jy_dl/jpg/Current/Image
* @return {count, size}
* @throws Exception
*/
public long[] checkFileSize(File file) throws Exception {
if(null==file)
throw new Exception(String.format("指定目录为null"));
if(!file.isDirectory())
throw new Exception(String.format("指定目录(%s)无效", file.getPath()));

File[] ff=file.listFiles();
long count=0;
long size=0;
for(File f : ff){
String name=f.getName().toLowerCase();
if(f.isDirectory()){
current++;
long[] tmp=checkFileSize(f);
count+=tmp[0];
size+=tmp[1];
String[] param={String.valueOf(current), String.valueOf(total), name, String.valueOf(count), String.valueOf(size)};
notify(param);
}else if(f.isFile()){
boolean flag=false;
if(name.lastIndexOf(".jpg")>=0 || name.lastIndexOf(".gif")>=0 || name.lastIndexOf(".tif")>=0)
flag=true;
if(!flag)
continue;
count++;
long d=f.length();
size+=d;
}
}
// System.out.println("file: "+file.getName());
// System.out.println(String.format("total file cnt: %d", cnt));
// System.out.println(String.format("total size: %d", amount));
long[] result={count, size};
return result;
}

/**
* 完成一个目录文件数量和容量计算后触发
* @param param {current, total, name, count, size}
*/
public void notify(String[] param){
for(IprocessAction e : listener){
e.actionPerom(param);
}
}

public void addEvent(IprocessAction e){
listener.add(e);
}

public void removeEvent(IprocessAction e){
listener.remove(e);
}

/**
* @param args the command line arguments
*/
public static void main(String[] args) throws Exception {
String name="网球图片";
File file=new File("E:\\图标素材");
// File file=new File("d:/hyt/test60w");
FileCounter fc=new FileCounter();
fc.addEvent(new IprocessAction() {

@Override
public void actionPerom(String[] param) {
double n=100*new Double(param[0])/new Integer(param[1]);
System.out.println(String.format("current: %s, total: %s, process: %.0f%%, name: %s", param[0], param[1], n, param[2]));
}
});
fc.checkFile(name, file);
}

interface IprocessAction {

/**
* 进度事件
* @param param {current, total, name, count, size}
*/
public void actionPerom(String[] param);
}
}



缩略版

/**
* 检查子图数量和容量的工具类<br />
* <br />
* 调用方法:<br />
* 1.FileCounter fc=new FileCounter()<br />
* 2.fc.addEvent(IprocessAction); 注册进度事件<br />
* 3.checkFile(name, file); 开始运算<br />
* 科目, 子图容量, 子图数量会显示在控制台<br />
* @author lizw
*/
public class FileCounter1 {
/**
* 在控制台打印科目子图容量和数量
* @param name 科目别名
* @param file 科目子图路径, 如//192.168.1.200/d$/2014yczk/YCZK/jykm/jy_dl/jpg/Current/Image
* @throws Exception
*/
public void checkFile(String name, File file)

/**
* 计算目录数量, 为统计子图数量的进度提供总数
* @param file
* @return
* @throws Exception
*/
public static long checkFileCount(File file)

/**
* 由于添加了进度事件, 此方法静态转成动态函数
* 计算图像的数和大小<br />
* 图像大小返回的是byte单位, 默认只统计(jpg, gif, tif)<br />
* 使用了递归算法
* @param file 科目子图路径, 如//192.168.1.200/d$/2014yczk/YCZK/jykm/jy_dl/jpg/Current/Image
* @return {count, size}
* @throws Exception
*/
public ong[] checkFileSize(File file)

}



面对这份代码, 我的问题就完全解决了, 而且不需要读代码, 除了注释里的代码

对比:

1.有动态函数和静态函数, 我要怎么使用呢, 这涉及到是否new FileCounter的问题, 这需要我去读代码
2.checkFileSize和checkFileSize1是怎么回事
3.checkFile, checkFileCount, checkFileSize, 看起来第一个函数包括了第2,3个函数的作用, 第2个看起来是计算文件数量, 第3个看起来是计算文件容量, 但我不敢肯定, 这需要我看一下checkFile的代码
4.公共域current和total是干什么用的
5.checkFile的输出? 为什么返回是void

重构注释后

1.从类的说明看到, 直接用动态方法, 还额外知道了, 有进度功能, 实现接口即可实现进度监控, 差点又跑去写一段进度监控代码, 原来一年前我已经想到计算时间太长的卡顿问题
2.checkFIleSize1, 是由于添加了进度事件, 从静态函数改动态函数的原因
3.由于第1点的关系, 我已经知道了直接调用checkFile即可, 而且上面注释已经告诉我, fileCount是计算文件夹目录而不是计算文件数量, fileFileSize是计算文件数量和容易, 而不是只是容量, 这里不需要再深入了, 只有出现bug时, 我才需要深入
4.current, total从注释中看出来是checkFileSize递归算法用的
5.不需要看代码就知道, checkFile的输出是控制台, 这个类是方便开发人员自己使用的

额外的
看着checkFileCount上面的"计算目录数量, 为统计子图数量的进度提供总数", 我不用看代码就回忆起来了, 进度的显示是按目录的进度, 而不是真实的文件数量的进度


综合例子2

public class Demo1 {

public void insertDataIntoDb(){
try {
Class c=Class.forName("com.mysql.jdbc.Driver");
c.newInstance();
String jdbc="jdbc:mysql://localhost/demo?user=root";
Connection con=DriverManager.getConnection(jdbc);
Statement stmt=con.createStatement();
String sql="select * from employee;";
ResultSet rs=stmt.executeQuery(sql);
while(rs.next()){
String[] text={rs.getString(2), rs.getString(3), rs.getString(4)};
System.out.println(Arrays.toString(text));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}


在insertDataIntoDb里没有关闭数据库连接
这个函数执行完后, 数据库连接会一直处于sleep状态, 造成严重的不可预知后果
要么数据库连接sleep到达上限, 由数据库回收连接
要么java虚拟机gc时清掉这个线程, 把数据库连接释放
如果遇到多几个这种代码, 数据库连接没能及时回归, 系统在状态下, 别的地方创建数据库失败就等着你, 特别是并发的情况, 这种错误通过静态单元测试是测不出来的
这是一个很可怕的bug
当然我们写代码时不可能时时刻刻想着大并发大压力的情况, 但资源的及时回收却是很容易做到的

改进

public class Demo1 {

public void insertDataIntoDb(){
try {
Class c=Class.forName("com.mysql.jdbc.Driver");
c.newInstance();
String jdbc="jdbc:mysql://localhost/demo?user=root";
Connection con=DriverManager.getConnection(jdbc);
Statement stmt=con.createStatement();
String sql="select * from employee;";
ResultSet rs=stmt.executeQuery(sql);
while(rs.next()){
String[] text={rs.getString(2), rs.getString(3), rs.getString(4)};
System.out.println(Arrays.toString(text));
}
con.close(); //在这里及时资源回收
} catch (Exception e) {
e.printStackTrace();
}
}
}


这样错误的, 如果sql语句错误, 就会在
ResultSet rs=stmt.executeQuery(sql);
这一行直接跳到exception catch
con.close()这行代码就会被忽略掉
同样造成资源没有及时回收的严重bug

正确的写法应该是写在
try catch 的 finally 里面


public class Demo1 {

public void insertDataIntoDb(){
Connection con=null;
try {
Class c=Class.forName("com.mysql.jdbc.Driver");
c.newInstance();
String jdbc="jdbc:mysql://localhost/demo?user=root";
con=DriverManager.getConnection(jdbc);
Statement stmt=con.createStatement();
String sql="select1 * from employee;";
ResultSet rs=stmt.executeQuery(sql);
while(rs.next()){
String[] text={rs.getString(2), rs.getString(3), rs.getString(4)};
System.out.println(Arrays.toString(text));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
con.close(); //在这里及时资源回收
} catch (SQLException ex) {
Logger.getLogger(Demo1.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值