通过应用单一责任原则来改变不良代码,不要重复自己,解耦...
#java #cleancode #体系结构 #opp

在这篇博客文章中,我们将将设计较差的代码转换为一个易于使用的系统。


设计较差的代码在使用时可能会导致挫败感,并且由于其在修改,重复和高耦合方面的难度,因此可以放慢项目的进度。

我们将通过应用原则来探讨如何克服这些挑战:

  • 单一责任原则
  • 不要重复自己
  • 脱钩

让我们卷起袖子,将不良代码转换为一个好的设计系统!

应用电子保健管理系统

我们将处理相对较小的Java控制台应用程序,因为进入一个大而复杂的应用程序将需要很多时间。它仍然足够大,可以证明现实世界中的错误和问题,并展示如何应用上述原则。

描述

e-Healthcare-Management-System是用于患者管理的控制台Java。

患者可以登录,注销,查看个人资料,查看医生,选择医生,查看约会,预约,给予反馈,查看报告,在线付款。

医生可以登录,注销,查看个人资料,查看约会,参加。

管理员可以登录,注销,查看配置文件,查看患者列表,添加/删除医生,查看医生列表,请参阅病人给出的反馈,查看报告。

完整的描述在这里

如何阅读这篇文章

要充分理解应用程序,您应该在GitHub存储库上检查其源代码。这是您需要知道的:

要了解代码如何演变,请审查两个分支。从applicationBeforeRefactor分支开始,以查看代码最初的外观,然后转到master分支。

此外,在阅读相关帖子或文章时,请定期参考源代码以更好地理解。

最初情况

注意:看看applicationBeforeRefactor分支。

i下几节我指出了代码的一些问题,然后我们看到如何通过应用SRP,干燥和去耦来解决它们。

软件包/文件夹和文件结构

当前文件夹结构在单个文件夹中具有所有类,因此很难确定哪个类属于哪个模块或组件。这可能会在编码过程中引起混乱,并使定位和修改特定类都具有挑战性。

All classes are in one folder

责任

    public void ShowPatientDetails(int id)/*This method all details of the patient*/
    {
        try {
            Connection con=ConnectionProvider.getCon();
            Statement st=con.createStatement();
            ResultSet rs=st.executeQuery("Select * from Patients where PatientID="+id);
            while(rs.next())
            { 
                System.out.println("PatientID:      "+rs.getInt(1));
                System.out.println("Name:           "+rs.getString(2)+" "+rs.getString(3));
                System.out.println("Blood-Group:    "+rs.getString(8));
                System.out.println("Address:        "+rs.getString(9));
                System.out.println("Contact-Number: "+rs.getString(5));
                System.out.print("\t********************\n");
            }
        }
        catch(Exception e)
        {
            System.out.println(e.getMessage());
        }
    } 

您看到此方法出了什么问题吗?

我相信当我说它做很多事情时,我同意我的看法:创建SQL查询,从DB获取数据,映射它,然后在UI上显示(在这种情况下为UI是System.out)。

类也是如此。其中一些示例是Main.javaAppointment.java ... **

我们看到每种方法和班级都有压倒性的责任,因此很难维护和修改。

此外,每个方法和类的大量代码可能具有挑战性,特别是对于不是原始作者的人。

不一致

    private String Doctor_Qualification;
    private int docFees;
    Scanner sc=new Scanner(System.in);

这是可变命名的示例,但是在项目中,我们有更多的矛盾之处。它们出现在:

  • 写评论
  • 命名变量和方法
  • 卷曲括号
  • 凹痕

这样的不一致不仅会给生产力带来障碍,因为理解代码需要更长的时间,并且适应它变得具有挑战性,而且还助长了更大的问题。





这些不一致会给人留下深刻的印象,这可能会导致混乱。当然,在这种情况下,您可能会问自己是否没有人在意,为什么我应该?

作者喜欢打字/编码。

    Connection con=ConnectionProvider.getCon();
    Statement st=con.createStatement();
    ResultSet rs=st.executeQuery("Select MAX(UserID) as NextUserID from Users where userType='Doctor'");
    // or
    st.executeUpdate("insert into Users values('"+DoctorID+"','"+"Doctor"+"','"+password+"')");

重复了几个地方和类似的代码块?

作者喜欢打字/编码,并且不知道他是重复代码,在某些地方更重要的是重复知识

< br> 从长远来看,这种做法使代码更难维护,并在软件应用程序的不同部分重复使用。此外,增加了在重复的代码块中引入错误的风险。

让我们改变不良代码

注意:看看master分支。

从哪儿开始

注意:这不是关于重构目标的文章,就是查看如何使SRC,干燥和去耦

受益

代码在一个提交中没有更改,而是逐步应用小更改。

在此步骤中,我们没有触摸代码。想法是为代码定义基本代码样式准则,例如:

  • 命名

  • 卷曲式样式

  • 凹痕

使我们在其中执行一致性。代码样式指南在README.md文件中

第二步,我们仍然没有编码

我从大局开始。 在模块/软件包级别应用单一责任原则。基于功能和当前代码,创建了新的软件包,并重新组织了类。这个想法是每个模块/软件包都有一个责任。

Applying Single responsibility principle at package level
注意:这不是最终版本,只是基于我拥有的知识的第一个版本

下一步解耦DB访问

经过仔细检查,我们可以看到,现在我们有一些看起来像模块的东西,但是它们是高度耦合的,并且大多数方法的责任太多了。

为了解决这个问题,我们通过实现简单的,受控的更改来将DB访问从代码中删除。

public final class Repository {

    private Connection connection;

    private Repository() {
        this.connection = ConnectionProviderDefault.getCon();
    }
    private Repository(Connection connection) {
        this.connection = connection;
    }

    private static Repository repository = new Repository();
    public static synchronized Repository getInstance() {
        if (repository == null) {
            repository = new Repository();
        }
        return repository;
    }

    @Deprecated
    public Connection getConnection() {
        return connection;
    }

    public boolean executeUpdate(String sql) {
      // check source code at git 
    }

    public <RESULT_ITEM> List<RESULT_ITEM> executeQuery(String query, Function<ResultSet, RESULT_ITEM> function) {
      // check source code at git 
   }
}

现在,我们使用Singleton Class Repository.java可以单点访问DB,也删除了许多重复代码。
这是用法:

Repository.getInstance().executeQuery("SELECT * FROM Appointments", mapDbRowToAppointment);

您是否注意到我是如何使用称为通过行为的技术,更多信息here

介绍业务层

将DB访问到单独的类后,UI和业务逻辑仍然高度耦合。让我们也从UI中移动业务逻辑。

这个想法是,每个模块/软件包应至少具有一个定义业务操作的接口,以及这些接口的默认实现。示例包括付款服务,反馈服务等。

这是PatientFeedback.java的示例

public interface PatientFeedback {
    PatientFeedback DEFAULT_INSTANCE = new PatientFeedbackImpl(Repository.getInstance());
    List<Feedback> getFeedbacks();
    boolean addFeedback(Feedback feedback);
}

请注意,裸露的接口可以访问其默认实现。

终于UI

尽管它是控制台应用程序,但UI的原理是相同的。每个模块/软件包将具有自己的类别名为Portal的类,该类别定义其视图(遵循单个责任原则)。然后,它们都是在主要方法中组成的。

public static void main(String[] args) {
    Config.getInstance().loadConfig(args == null || args.length < 1 ? null : args[0]);

    Scanner sc = new Scanner(System.in);
    AdminPortal adminPortal = new AdminPortal();
    PatientPortal patientPortal = new PatientPortal();
    DoctorPortal doctorPortal = new DoctorPortal();
    PatientRegistrationPortal patientRegistrationPortal = new PatientRegistrationPortal();

我想通过创建可重复使用的数据表来减少UI组件的重新实现,特别是该应用程序中的数据表。

public final class TerminalTablePrinter {
    private TerminalTablePrinter() {}
    public static <ELEMENT> void printTable(List<String> header, List<ELEMENT> rows, Function<ELEMENT, List<String>> mapper) {

此外,我创建了一个登录组件LoginTerminal.java和一个用于插入用户数据PersonTerminal.java的组件,可以在整个代码中重复使用。这些可重复使用的UI组件放在terminalUtil包中。

我们完了吗

代码中有很多事情应该得到改进:

  • 例外处理
  • Statement替换为PreparedStatement(db访问)
  • db
  • 的命名约定
  • 验证层
  • 外部政党自由 - 验证信用卡

应用原则:

  • 单一责任原则
  • 不要重复自己
  • 脱钩

我们设法将设计不佳的代码转换为一个易于使用的精心设计的系统,因此我正在结束此代码转换。

但是,这是当代码精心设计时实现改进的示例。

示例用PreparedStatement替换Statement(DB访问)

因为用于访问数据库的代码与应用程序的其余部分是截然不同的,但没有重复,并且只有一个责任,因此很容易引入更改。

在实践中,代码的所有更改都将在Repository.java类中发生

    public boolean executeUpdate(String sql) {
        this.checkConnection();
        try {
            PreparedStatement st= this.connection.prepareStatement(sql);
            st.executeUpdate(sql);
            return true;
        }catch(Exception e){
            e.printStackTrace();
            return false;
        }
        finally {
            try {
                this.connection.close();
            } catch (SQLException e) {
                throw new RepositoryConnectionCloseException();
            }
        }
   }

进行练习。可以随意实施此或其他一些改进,共享您的代码,我们可以讨论它。

最后一句话

良好的代码设计对于软件应用程序的可维护性,开发人员的生产率至关重要。

通过应用关键设计原则,例如单个责任原则,不要重复自己并将其脱钩到设计较差的代码,我们将效率低下的混乱代码转换为一个易于使用的精心设计的系统。

我们已经看到了如何创建模块,每个模块都具有单一的责任,可以与UI脱钩的业务逻辑,以及可重复使用的UI组件如何减少代码复制。按照代码样式准则以及在命名,凹痕和卷发括号中执行一致性的增量更改可以带来更好的代码质量。

通过遵循这些原则,我们可以降低引入错误的风险,使我们的代码在开发软件应用程序时更容易理解并提高生产率。