您当前的位置:首页 > 电脑百科 > 程序开发 > 程序员

如何成为一个更好的程序员?设计原则I:KISS,DRY...

时间:2020-07-14 09:39:09  来源:  作者:

 

如何成为一个更好的程序员?设计原则I:KISS,DRY...

 

当我开始编程时,我很高兴自己的程序正在编译并且可以按预期的方式运行,但是随着时间的推移,我编写了越来越多的代码,我开始欣赏设计模式。

设计模式不仅使我的代码更好,更易读并且更易于维护,而且还为我节省了很多时间,我的意思是要花费大量的调试时间。 因此,我想与您分享一些内容。

这种设计模式源自" DesignPatterns书"的作者"四人帮"。 他们介绍了这些原理,这些原理在面向对象编程中特别有用。 可能您已经在使用所有这些内容,但是刷新您的知识总是好的。

所有代码示例均在TypeScript中。

KISS:保持简单愚蠢

这种模式的主要概念是保持尽可能简单的代码。 这意味着每个方法,类,接口的名称应尽可能清楚,函数,方法等内部的逻辑也应尽可能简单明了。

KISS看起来不像的示例:

class payup {

  howmuchtopay(region: string, amount: number, tax: number, country: string, price: number) {

    if (country == "pl_PL") {
      if (region == "masovia" || region == "Lubusz") {
        if (amount > 15) {
          price -= ((15/100)*price);
          price += ((tax/100)*price);
          return (price * amount);
        }
        return ((price+((tax/100)*price)) * amount);
      } else {
        return ((price+((tax/100)*price)) * amount);
      }
    } else {
      return (price*amount);
    }

  }

}


const p = new payup();
console.log( p.howmuchtopay("masovia", 25, 23, "pl_PL", 1000) );

在此示例中,我们的代码什么都没有告诉我们。 类名,方法名写不正确。 方法的主体混乱不堪,如果不能维护则很多。

使用KISS原理后,它看起来像这样:

interface Country {
  code: string;
  discountAmountPercent: number;
  taxAmountPercent: number;
  discountRegions: Array<string>;
}

class Poland implements Country {
    code: string = "pl_PL";
    discountAmountPercent: number = 15;
    taxAmountPercent: number = 23;
    discountRegions: Array<string> = [
      "masovia",
      "lubusz"
    ];
}

class Payment {

     setTax(price: any, tax: number) {
       return (price + (tax/100*price));
     }

     setDiscount(price: any, discount: number) {
       return (price - ((discount/100)*price));
     }

     pay(country: Country, region: string, amount: number, nettoPrice: number) {

       if (
         country.discountRegions.indexOf(region.toLowerCase()) != -1
         && amount > 15
       ) {
         nettoPrice = this.setDiscount(nettoPrice, country.discountAmountPercent);
       }

       const bruttoPrice = this.setTax(nettoPrice, country.taxAmountPercent);
       return (bruttoPrice*amount);
     }
}

const payment = new Payment();
console.log ( payment.pay((new Poland), 'masovia', 25, 1000) );

您可以在上面的代码中找到KISS原理。 这是可扩展的解决方案:我们可以有多个国家/地区,并轻松添加新的折扣区域,只需在国家/地区类别中修改DiscountRegions属性即可。 由于有了界面,我们还可以确保每个新国家都具有必需的属性。 支付类还具有以其功能命名的方法,由于这种结构,我们优化了代码,使其只有一个。

那就是亲吻:干净,简单的代码。

DRY:不要重复自己

这个概念建议我们将执行相同操作的代码划分为小部分。 有时,我们有多行代码基本上都在做同样的事情:例如使用指定条件按数组过滤,向对象添加一些东西等。通常的好习惯是摆脱它。

不良DRY代码的示例是:

class Week {
  days: any;
  constructor() {
    this.days = [];
    this.days.push({
      name: "Monday",
      order: 1
    });
    this.days.push({
      name: "Tuesday",
      order: 2
    });
    this.days.push({
      name: "Wednesday",
      order: 3
    });
    this.days.push({
      name: "Thursdya",
      order: 4
    });
    this.days.push({
      name: "Friday",
      order: 5
    });
    this.days.push({
      name: "Saturday",
      order: 6
    });
    this.days.push({
      name: "Sunday",
      order: 7
    });
  }

  list() {
    console.log(this.days);
  }
}

const w = new Week();
w.list();

在此示例中,我们添加了多天,并且类的代码基本相同。 我们可以通过创建用于此目的的方法来避免这种情况。 同样,通过多次手动输入日期名称,我们扩展了出错的可能性。

具有良好DRY代码的适当类的示例:

enum dayNames {
  Monday = "Monday",
  Tuesday = "Tuesday",
  Wednesday = "Wednesday",
  Thursday = "Thursday",
  Friday = "Friday",
  Saturday = "Saturday",
  Sunday = "Sunday"
}

class Day {
  name: string;
  order: number;

  constructor(name: string, order: number = 0) {
    this.name = name;
    this.order = order;
  }

  setOrder(order: number) : Day {
    this.order = order;
    return this;
  }

}

class Week {

  days: Array<Day> = new Array();

  private addDay(name: string): Day {
    const day = new Day(name);
    const index = this.days.push(day);
    day.setOrder(index)
    return day;
  }

  constructor() {
     for(let dayName in dayNames) {
       this.addDay(dayName);
     }
  }

  listDays() {
    console.log(this.days);
  }

}

const firstWeek = new Week();
firstWeek.listDays();

在此示例中,我们没有使用每天手动输入的方式来实现带有预定义日期名称的枚举,并且还引入了Day类。 因此,我们可以扩展它以在将来向此类添加更多功能,例如getDaylightTime。 此外,我们还为Week类实现了addDay方法,该方法的作用几乎相同,但是现在,如果发生任何更改,我们只需在代码中更新一个位置即可,而不是更新七个位置。

这是DRY原则。

TDA:告知而不要询问

该原则建议我们应该以一种使对象行为而不是对象处于何种状态的方式来编写代码。这可以避免类之间不必要的依赖关系,这要归功于它们具有更易于维护的代码。 它与代码封装紧密相关。

没有TDA原理的代码示例:

class User {
    _id: string = '';
    firstName: string = '';
    lastName: string = '';
    tokens: number = 0;
}

class UserService {

    register(firstName: string, lastName: string): User {

        if ( firstName.length < 3 ) {
            throw new Error("Name is not long enough.");
        }

        if ( lastName.length < 3) {
            throw new Error("Name is not long enough");
        }

        const user = new User();
        user._id = Math.random().toString(36).substring(7);
        user.firstName = firstName.toLowerCase();
        user.lastName = lastName.toLowerCase();

        return user;
    }

    updateTokens(user: User, operation: string, amount: number): User {

        if (operation == 'add') {
            user.tokens += amount;
        }

        if (operation == 'sub') {
            if (user.tokens - amount >= 0 ) {
                user.tokens -= amount;
            } else {
                user.tokens = 0;
            }
        }

        return user
    }

}

const uService = new UserService();
const u = uService.register("John", "Smith");
uService.updateTokens(u, 'add', 1000);
console.log( u );
uService.updateTokens(u, 'sub', 1100);
console.log( u );

正如我们可以看到UserService多次访问User对象属性一样,特别是在更新User.tokens时,如果我们在程序的许多部分中都拥有该功能并且想要更改其工作方式的逻辑呢,还要看看验证器:所有逻辑 它的行为方式在方法内部,但是我们应该使其更具可伸缩性和可重用性。 下面是一个示例如何执行此操作。

TDA示例:

/**
 * VALIDATORS
 */
class StringLengthValidator {
    static greaterThan(value: string, length: number): boolean {
        if ( value.length > length)  {
            return true
        } else {
            throw new Error("String is not long enough.");
        }
    }
}

class NaturalNumberValidator {
    static operation(from: number, amount: number) {
        if (from + amount <= 0) {
            return 0;
        }
        return from + amount;
    }
}

/**
 * INTERFACES
 */
interface IUserAccount {
    _id: string;
    firstName: string;
    lastName: string;
    tokens: number;
}

/**
 * ENUMS
 */
enum operations {
    add = 'add',
    sub = 'sub'
}

/**
 * CLASSES
 */
class User implements IUserAccount {

    _id : string = '';
    firstName: string = '';
    lastName: string = '';
    tokens: number = 0;

    constructor(firstName, lastName) {
        this._id = this._generateRandomID();
        this.setFirstName(firstName);
        this.setLastName(lastName);
    }

    setFirstName(newFirstName: string): User {
        StringLengthValidator.greaterThan(newFirstName, 3);
        this.firstName = newFirstName;
        return this;
    }

    setLastName(newLastName: string): User {
        StringLengthValidator.greaterThan(newLastName, 3);
        this.lastName = newLastName;
        return this;
    }


    updateTokens(amount: number): User {
        this.tokens = NaturalNumberValidator.operation(this.tokens, amount);
        return this;
    }

    private _generateRandomID() {
        return Math.random().toString(36).substring(7);
    }

}

class UserService {

    register(firstName: string, lastName: string): User {
        let user : User = null;

        try {
            user = new User(firstName, lastName);
        } catch (e) {
            console.log(e);
        }

        return user;
    }

    updateTokens(user: User, operation: operations, amount: number): User {

        if (operation === operations.sub) {
            amount *= -1;
        }

        return user.updateTokens(amount);
    }

}

/**
 * PROGRAM
 */
const uService = new UserService();
const u = uService.register("john", "smith");
uService.updateTokens(u, operations.add, 1000);
console.log(u);
uService.updateTokens(u, operations.sub, 1100);
console.log(u);

乍一看,似乎似乎过于复杂,需要更多代码。但是,长远来说,感谢封装和独立的验证器,我们可以在许多通用情况下多次使用它。 User类的属性仅在其内部使用,UserService正在调用高级方法来访问它。 因此,我们将所有逻辑都放在一个地方,因此当我们要在其他地方使用User类时,程序将按预期运行。

SoC:关注点分离

这个原则告诉我们将一个班级的责任划分给这个班级和仅将这个班级的责任分开。 对象不应共享其功能。 每个类都应该是唯一的,并且应与其他类分开。

不带SoC的代码示例:

class User {
    _id: string = '';
    name: string = '';
    balance: number = 0;


    constructor(name) {
        this._id = Math.random().toString(36).substring(7);
        this.name = name;
    }
}

class AccountService {

    log(msg: string) {
        console.log((new Date()) + " :: " + msg);
    }

    transfer(user1: User, user2: User, amount: number): any {
        // validate amount
        if ( amount <= 0 ){
            this.log("amount 0, nothing changed.");
            return {user1, user2};
        }

        // validate if user1 have enough
        if ((user1.balance - amount) < 0) {
            this.log("user " + user1._id + " did not have enough funds.");
            return {user1, user2};
        }

        //get from user1
        user1.balance -= amount;
        // add to user2
        user2.balance += amount;

        this.log("User " + user1._id + " now has " + user1.balance);
        this.log("User " + user2._id + " now has " + user2.balance);

        return {user1, user2};
    }

    updateBalance(user: User, amount: number): User {
        user.balance += amount;
        this.log("User " + user._id + " now has " + user.balance);
        return user;
    }

}

const aService = new AccountService();
let u1 = new User("john");
u1 = aService.updateBalance(u1, 1000);

let u2 = new User("bob");
u2 = aService.updateBalance(u2, 500);

console.log( aService.transfer(u1, u2, 250) );

我们拥有AccountService,它负责多项工作:记录,验证和操作用户余额。 同样,未实施TDA。 我们应该分开验证并创建外部记录器,以备将来在其他模块中使用。

适当的SoC的示例:

/**
 * VALIDATORS
 */
class StringLengthValidator {
    static greaterThan(value: string, length: number): boolean {
        if ( value.length > length)  {
            return true
        } else {
            throw new Error("String is not long enough.");
        }
    }
}

class UserBalanceValidator {
    static haveEnoughFunds(user: User, amount: number): boolean {
        return (user.getBalance() - amount) > 0;
    }
}

/**
 * INTERFACES
 */
interface IUserAccount {
    _id: string;
    name: string;
    balance: number;
}

/**
 * CLASSES
 */
class User implements IUserAccount {

    _id: string = '';
    name: string = '';
    balance: number = 0;

    constructor(name) {
        this._id = this._generateRandomID();
        this.setName(name);
    }

    private _generateRandomID() {
        return Math.random().toString(36).substring(7);
    }

    getId(): string {
        return this._id;
    }

    setName(name: string): User {
        StringLengthValidator.greaterThan(name, 2);
        this.name = name;
        return this;
    }

    getBalance(): number {
        return this.balance;
    }

    setBalance(amount: number): User {
        this.balance = amount;

        LoggerService.log("User " + this.getId() + " now has " + this.getBalance() );
        return this;
    }
}

class LoggerService {

    static log(message: string): string {
        message = (new Date()) + " :: " + message;
        console.log(message);
        return message;
    }

}

class AccountService {


    transfer(fromUser: User, toUser: User, amount: number): any {
        if (!UserBalanceValidator.haveEnoughFunds(fromUser, amount)) {
            LoggerService.log("User " + fromUser.getId() + " has not enough funds.");
            return {fromUser, toUser};
        }

        fromUser.setBalance(fromUser.getBalance() - amount);
        toUser.setBalance(toUser.getBalance() + amount);

        return {fromUser, toUser}
    }

    updateBalance(user: User, amount: number) : User {
        user.setBalance(user.getBalance() + amount);
        return user;
    }

}

const aService = new AccountService();
let u1 = new User("john");
let u2 = new User("bob");

u1 = aService.updateBalance(u1, 1000);
u2 = aService.updateBalance(u2, 500);

console.log( aService.transfer(u1, u2, 250) );

现在我们每个类都有各自的状态和功能:验证器,用户,AcocuntService和LoggerService。由于SoC,我们可以在应用程序的许多不同模块中分别使用它。 而且,由于存在逻辑的位置较少,因此该代码更易于维护。

YAGNI:您将不需要它

该原则不是在告诉我们如何直接在代码中执行某些操作,而是告诉我们如何有效地进行编码。 总的说来,我们应该只编写在特定时刻需要我们编写的内容。 例如:当我们必须对电子邮件和密码字段进行验证时,我们不应该对名称进行验证,因为我们可能永远不需要它。 确实是TDD:我们仅针对微功能编写测试,并为此编写最少的代码。 " YAGNI"正试图节省我们的时间,专注于冲刺中最重要的事情。

那就是所有的内容

我希望通过这篇文章,您学到了一些东西,或者提醒了您已经知道的一些东西。 :)

 



Tags:程序员   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
我 2010 年开始在 Github 上开源自己的代码。在 push 代码之前我根本没想过为什么。只是因为我当时学了 git,而且我又觉得 Github 很方便,可以用来备份自己的代码。而后我就参...【详细内容】
2021-12-28  Tags: 程序员  点击:(2)  评论:(0)  加入收藏
JAVA开发工程师(北京)本科 3-5年经验 面议 (招1人)岗位职责:1.负责我行应用系统的设计,完成软件编码工作,负责管理代码设计规范等工作;2.根据应用需求分析说明书,评估需求研发的可行...【详细内容】
2021-12-27  Tags: 程序员  点击:(2)  评论:(0)  加入收藏
今天聊聊编程的本质。程序就是数据结构+控制+逻辑,程序员编程工作的本质是翻译,翻译机要来了,程序员怎么办?黑客帝国中的程序黑客帝国4就要上映了,不知道前三部你看懂了么?值得多...【详细内容】
2021-12-17  Tags: 程序员  点击:(9)  评论:(0)  加入收藏
什么是shellshell是c语言编写的程序,它在用户和操作系统之间架起了一座桥梁,用户可以通过这个桥梁访问操作系统内核服务。 它既是一种命令语言,同时也是一种程序设计语言,你可以...【详细内容】
2021-12-16  Tags: 程序员  点击:(18)  评论:(0)  加入收藏
我是一名程序员关注我们吧,我们会多多分享技术和资源。进来的朋友,可以多了解下青锋的产品,已开源多个产品的架构版本。Thymeleaf版(开源)1、采用技术: springboot、layui、Thymel...【详细内容】
2021-12-14  Tags: 程序员  点击:(21)  评论:(0)  加入收藏
梦醒之后,每个人对于这份职业的未来、互联网行业的未来,以及更重要的,自己的未来都有了更现实的判断 文 | 祝颖丽编辑 | 黄俊杰一个生于 1986 年的人,他所走过的前半生:从出生起,...【详细内容】
2021-12-03  Tags: 程序员  点击:(16)  评论:(0)  加入收藏
前些天在头条看到一个八二年的哥们,述说自己找工作屡次被拒的问题,在网上引起了广泛的讨论,这件事给我留下了很深的印象,因为这哥们和我同是程序员,都人到中年,上有老下有小。唯一...【详细内容】
2021-12-01  Tags: 程序员  点击:(20)  评论:(0)  加入收藏
很多读者都问过一个问题:程序员如何实现高速成长?之前也写过相关的文章,强调的主要是夯实计算机体系基础知识。 再说另一个诀窍:多看经典开源项目,这些项目大多是众多顶尖程序员...【详细内容】
2021-11-30  Tags: 程序员  点击:(15)  评论:(0)  加入收藏
序言:前段时间织梦因为版权的问题在网上闹得沸沸扬扬,也提醒了众多开发者选择cms上应该谨慎使用,今天给大家展示一款自己搭建的内容管理系统,不用担心版权的问题,而且非常容易维...【详细内容】
2021-11-30  Tags: 程序员  点击:(34)  评论:(0)  加入收藏
近日,一位45岁的网民在中国政府网留言求职,引发关注。该网民自称是一名软件开发人员,今年45岁,精通各种技术体系,“而我辞职回家半年后再回来寻找工作机会的时候,却发现连个面试...【详细内容】
2021-11-17  Tags: 程序员  点击:(42)  评论:(0)  加入收藏
▌简易百科推荐
我 2010 年开始在 Github 上开源自己的代码。在 push 代码之前我根本没想过为什么。只是因为我当时学了 git,而且我又觉得 Github 很方便,可以用来备份自己的代码。而后我就参...【详细内容】
2021-12-28  程序员的喵    Tags:Github   点击:(2)  评论:(0)  加入收藏
JAVA开发工程师(北京)本科 3-5年经验 面议 (招1人)岗位职责:1.负责我行应用系统的设计,完成软件编码工作,负责管理代码设计规范等工作;2.根据应用需求分析说明书,评估需求研发的可行...【详细内容】
2021-12-27  just do丶IT公众号    Tags:国企   点击:(2)  评论:(0)  加入收藏
今天聊聊编程的本质。程序就是数据结构+控制+逻辑,程序员编程工作的本质是翻译,翻译机要来了,程序员怎么办?黑客帝国中的程序黑客帝国4就要上映了,不知道前三部你看懂了么?值得多...【详细内容】
2021-12-17  博士聊IT    Tags:程序员   点击:(9)  评论:(0)  加入收藏
梦醒之后,每个人对于这份职业的未来、互联网行业的未来,以及更重要的,自己的未来都有了更现实的判断 文 | 祝颖丽编辑 | 黄俊杰一个生于 1986 年的人,他所走过的前半生:从出生起,...【详细内容】
2021-12-03    财经杂志  Tags:程序员   点击:(16)  评论:(0)  加入收藏
前些天在头条看到一个八二年的哥们,述说自己找工作屡次被拒的问题,在网上引起了广泛的讨论,这件事给我留下了很深的印象,因为这哥们和我同是程序员,都人到中年,上有老下有小。唯一...【详细内容】
2021-12-01  云南贤哥在深圳    Tags:程序员   点击:(20)  评论:(0)  加入收藏
很多读者都问过一个问题:程序员如何实现高速成长?之前也写过相关的文章,强调的主要是夯实计算机体系基础知识。 再说另一个诀窍:多看经典开源项目,这些项目大多是众多顶尖程序员...【详细内容】
2021-11-30  findyi    Tags:程序员   点击:(15)  评论:(0)  加入收藏
近日,一位45岁的网民在中国政府网留言求职,引发关注。该网民自称是一名软件开发人员,今年45岁,精通各种技术体系,“而我辞职回家半年后再回来寻找工作机会的时候,却发现连个面试...【详细内容】
2021-11-17  郭主任    Tags:程序员   点击:(42)  评论:(0)  加入收藏
即使在安全技术取得进步之后,网络犯罪仍在不断增加。据统计,网络犯罪每分钟给企业造成约 290 万美元的损失。主要是因为新技术不断涌现,难以维护安全。随着网络威胁的增加,网络...【详细内容】
2021-11-04  章大千    Tags:编程语言   点击:(40)  评论:(0)  加入收藏
北漂小伙李强(化名),在北京互联网大厂工作7年,月薪3万,离职回老家开摄影店,亏了200万。李强出生于山西一座名不经传的小城市,互联网专业大学毕业的他,没有听父母的劝言回到家乡考公...【详细内容】
2021-10-29  霸王课  今日头条  Tags:程序员   点击:(53)  评论:(0)  加入收藏
程序员是青春饭,这在国内似乎是公认的。所以很多公司不愿招大龄程序员,很多程序员也“知趣”地及早转型。有的做管理,有的做架构,我还见过改行卖保险的。总之,年龄大了不想敲代码...【详细内容】
2021-10-27  编程的艺术    Tags:   点击:(30)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条