蘭陵N梓記

一指流沙,程序年华


  • 首页

  • 归档

  • 关于

  • 搜索
close

程序员编码技术栈

时间: 2020-11-15   |   分类: 技术     |   阅读: 4967 字 ~10分钟

写在前面

偶尔会有同学来问我:“飞哥,我在学校是学java,来公司却安排搞c++,我不喜欢,怎么办?”

让人信服地回答这个问题其实很难。我的理解是无论什么语言,本质是要服务业务,无论是java还是c++,他们都是实现业务软件的工具。在工作中,我们要尽可能多的去掌握一些固化不变的基础,越基础的知识点越具有通用性,掌握会让自己变得更有竞争力。各种编程语言在语法上可能有着相同又有不同的地方,有着他们各自最佳适合的场景。透过语法层面来看,似乎我们总是能找到一些共同的基础知识体系。当你了解并熟悉这些知识点之后,可能不会再纠结是选java还是其它了。

当然并不是语言层面的知识不再需要深入掌握,而是当工作选择需要你掌握哪种语言时,就去学习了解哪种语言。有了一些公共基础,也会触类旁通,学习起来更轻松。

本文试图抛开具体的语言,列出提纲梳理背后共同的技术栈,希望能给正在处于纠结选择语言的同学一些帮助。由于笔者能力、精力都有限,部分内容也是从网上收集整理,其中可能存在理解上偏差与错误。

阅读全文 »

飞哥讲代码16:函数式让数据处理更简洁

时间: 2020-11-08   |   分类: 技术     |   阅读: 2514 字 ~6分钟

案例

案例一,代码摘抄某外部培训材料,主要代码逻辑是打印每课成绩,并找出学生非F级别课程统计平均分数:

class CourseGrade {
    public String title;
    public char grade;
}

public class ReportCard {
    public String studentName;
    public ArrayList<CourseGrade> cliens;

    public void printReport() {
        System.out.println("Report card for " + studentName);
        System.out.println("------------------------");
        System.out.println("Course Title       Grade");
        Iterator<CourseGrade> grades = cliens.iterator();
        CourseGrade grade;
        double avg = 0.0d;
        while (grades.hasNext()) {
            grade = grades.next();
            System.out.println(grade.title + "    " + grade.grade);
            if (!(grade.grade == 'F')) {
                avg = avg + grade.grade - 64;
            }
        }
        avg = avg / cliens.size();
        System.out.println("------------------------");
        System.out.println("Grade Point Average = " + avg);
    }
}

上面的代码有哪些问题呢:

  • 成员变量采用public,缺少数据封装性
  • 没有判断cliens是否为空,可能除以0值。注:假定它不会为空,另外逻辑可能有问题,为什么统计总分是非F课程,除数却是所有课程Size,先忽略这个问题
  • avg这个变量多个用途,即是总分,又是平均分
  • cliens变量名难以理解
  • !(grade.grade == 'F') 有点反直觉
  • while循环干了两件事,打印每课的成绩,也统计了分数
阅读全文 »

飞哥讲代码15:写代码从事物认识开始

时间: 2020-11-01   |   分类: 技术     |   阅读: 2417 字 ~5分钟

案例

上周参加张逸老师解构领域驱动设计培训。课上老师提到传统的设计是贫血模型类+事务脚本(逻辑过程),并给出一个贫血类设计的案例代码。凭记忆记录如下,有三个类:

  • Customer: 顾客
  • Wallet: 顾客的钱包
  • Paperboy: 收银员

实现的主体逻辑是,收银员向顾客收钱。

代码如下:

public class Wallet {
    private float value;

    // 省略构造方法

    public float getTotalMoney() { return value; }
    
    public void addMoney(float deposit) {
        value += deposit;
    }

    public void subtractMoney(float debit) {
        value -= debit;
    }
}

public class Customer {
    private String firstName;
    private String lastName;
    private Wallet myWallet;

    // 省略构造方法,与Getter
}

public class Paperboy {
    public void charge(Customer myCustomer, float payment) {
        Wallet theWallet = myCustomer.getWallet();
        if (theWallet.getTotalMoney() > payment) {
            theWallet.subtractMoney(payment);
        } else {
            throw new NotEnoughMoneyException();
        }
    }
}
阅读全文 »

飞哥讲代码14:好代码源自相互改进

时间: 2020-09-20   |   分类: 技术     |   阅读: 2207 字 ~5分钟

案例

下面的代码是来自我们新构建的服务,采用Python语言开发。案例故事是这样:

开始: 某同学最先开发某功能,需要读取服务的配置文件,代码如下,是代码直接读取文件取配置项:

HOME_PATH = os.environ['HOME']
DASK_PROPERTIES_PATH = HOME_PATH + '/training/webapps/lodas/WEB-INF/classes/application-dask.properties'
DASK_PROPERTIES_DICT = Properties(DASK_PROPERTIES_PATH).get_properties()

CLUSTER_WORKER_THREAD_NUM = DASK_PROPERTIES_DICT['lodap.dask.cluster.worker.nthreads']
.... # 省略其它配置项的获取代码

后续: 功能不断增加,又分配给 不同的同学来实现 ,也需要读取同目录下其它的配置文件,于是又出现 类似 上面的代码写法,但略有差别,就不再贴代码了。

问题: 经过一段时间,发现类似读取配置项的代码段 散落 到我们源码中多个地方。从功能上讲,代码也没什么问题;但从可维护角度来看,若后面对配置增加约束或者配置文件挪位置,侧需要修改多处。

重构: 对它的改进也很简单,对一个服务的多个配置文件集中管理,提供 封装 对象。改进之后代码如下,并且做了一点小的容错性增强:

  • [1] 检查配置文件存在时才加入dict中,解决当文件不存在时,直接调用Properties(file)抛异常问题。
  • [2] 当配置项不存在时,支持默认值,解决代码中直接对Dict取下标操作时当不存在Key抛异常问题。
阅读全文 »

飞哥讲代码13:好代码须匠心打磨

时间: 2020-09-12   |   分类: 技术     |   阅读: 2985 字 ~6分钟

案例

目前写Python的同学越来越多了,但动态语言无类型约束,导致Commit时难以review。先来看一段我们的代码:

优化前的代码:

    def handle(self, data, rules):
        rule_type = rules['type'] # 1
        rule_list = rules['rules'] # 2
        res = None

        if str(rule_type).lower() != RULES_MAPPING:
            raise ValueError("type of rule should be 'rules_mapping'")

        for rule in rule_list:
            col_name = rule['column'] # 2
            if rule['function'].lower() == 'cast': # 3
                mapping_dict, other = self.parse_cast(rule['mapping'])
                res = data[col_name].apply(self.case_func, args=(mapping_dict, other))
            elif rule['function'].lower() == 'in':
                mapping_dict, other = self.parse_in(rule['mapping'])
                res = data[col_name].apply(self.in_func, args=(mapping_dict, other))
            elif rule['function'].lower() == 'binning':
                res = data[col_name].apply(self.binning_func, args=(rule['mapping']))
            else:
                raise ValueError('not supported function') # 4 
        return res

代化之后的代码:

阅读全文 »

飞哥讲代码12:好代码应表意直白

时间: 2020-08-15   |   分类: 技术     |   阅读: 2214 字 ~5分钟

案例

下面代码都来源于部门某一中间件产品(java)的源码,代码风格(此风格非格式风格而是逻辑思维风格)并且在整个源码中具有普遍性。

代码一:

public void run() {
    while(!(this.stopped.get())) {
        try {
            synchronized (this.lock) {
                while (this.endTime - System.currentTimeMills() > 0L) {
                    try {
                        if (this.stopped.get()) {
                            return;
                        }
                        this.lock.wait(this.endTime - System.currentTimeMillis());
                        // 省略主要业务逻辑
                    } catch (IntrruptedExecption e) {
                        // 省略日志打印
                    }
                } // 注意:这个while之后并没有其它的逻辑
            }
        } catch(Throwable e) {
            // 省略日志打印
        }
    }
}

代码意思是可能在等待的最大时间内,中间可以被通知执行主逻辑,然后再进入等待下次通知。在易读性上的问题:

  • 双重while, 双重try/catch,增加代码嵌套层次,代码有六层,由于跨度较大,掩盖了要表达的业务逻辑,不容易看懂。
  • 第一个while判断是 否定之否定 判断,不够直接,stoppped不需采AtomicBoolean,使用volatile变量即可。
  • 代码有bug(嵌套太深隐藏了bug),当超过最大时间时,若没有设置stopped标识位,空循环占CPU。

建议优化:

  • 由于synchronized是对lock的wait方法同步,wait后面的逻辑并不需要再同步保护,不应该锁整个while,减少锁的粒度。可以对wait逻辑单独抽取一个方法,直白表示是要waitNotify。
  • 去掉否定之否定。把AtomicBoolean stopped变成volatile boolean running,判断更直白,running表示还得继续。
阅读全文 »

飞哥讲代码11:通过封装降低耦合

时间: 2020-08-08   |   分类: 技术     |   阅读: 2500 字 ~5分钟

案例

最近在走读某一老产品的代码,发现存在一个普遍不好的实践,代码类似如下:

public class Class1 {
    private Map<String, String> store = new HashMap<>();
    private List<Class2> queue = new ArrayList<>();
    //... 省略其它的字段与其Getter/Setter方法
}

此类的特点是:只有一些集合字段与其Getter/Setter,而对字段的使用却是如下:

public void method1() {
  // ... 省略其它逻辑

   // 在其它的类中的方法实现中,却通过Getter方法获取集合对象加锁来处理
   synchronized(class1.getStore()) {
     String value = class1.getStore().get(key);
     if (value == null) {
        // ... 省略其它逻辑
        value = createValue();
        class1.getStore().put(key, value);
     }
   }

   // ... 省略其它逻辑
}

代码的问题是很明显:

  • Class1中的成员直接被Get出去,散落在各个类中操作,缺少对其操作的方法封装,破坏了类的封装性,带来了数据的耦合。
  • 同步加锁在Owner对象之外,其出发点是以其它方法逻辑为切入,而不是从Owner对象的数据全生命周期安全来思考,很容易造成加锁不全。
阅读全文 »

飞哥讲代码10:提升性能,表设计很重要

时间: 2020-07-26   |   分类: 技术     |   阅读: 3080 字 ~7分钟

案例

在这个月,我曾经分析处理两个与数据操作相关的性能问题。根因是缺少对表的严谨设计。通过搜索些资料,便有此博文分享给大家。

案例一:某服务对接Oracle,在某些场景下出现读取表数据失败。现象是日志会报如下堆栈信息:

Caused by java.sql.SQLException: Stream has already bean closed
at oracle.jdbc.driver.LongAccessor.getBytesInternel(LongAccessor.java:127)
at oracle.jdbc.driver.Accessor.getBytes(Accessor.java:897)
at oracle.jdbc.driver.LongAccessor.getString(LongAccessor.java:154)
...

从堆栈来看,是访问Long类型的字段值(通过LongAccessor字面猜出),获取Bytes的流强制关闭了,为什么有时会关闭,过长过大?通过Google搜索才发现,我们跳入使用Long类型坑中。

阅读全文 »
1 2 3 4 5 6 7 8
兰陵子

兰陵子

Programmer & Architect

164 日志
4 分类
57 标签
RSS 订阅
GitHub 知乎
© 2009 - 2022 蘭陵N梓記
Powered by - Hugo v0.101.0
Theme by - NexT
0%