首页 > 其他分享 >如何提高你的代码质量?

如何提高你的代码质量?

时间:2023-05-17 15:37:11浏览次数:33  
标签:return 代码 param 如何 质量 Integer null 异常 public

1 可读性

1.1 准确命名

每种编程语言都有自己的命名规范,不同语言的风格差异有大有小,下面以Java为例:

Java整体命名风格为UpperCamelCase或lowerCamelCase形式。不管是类还是变量命名要见名知意,切勿使用缩写或中文;风格统一,尽量使用英文名词,切勿中英文混合;尽量避免和Java自带类库重名,切勿使用Java关键字命名。

  • 包命名规范

包命名使用小写英文名词,使用"."分割,每个被分割的单元尽量只有一个名词,命名规范为:

域名.公司/个人名称.项目名称.模块名称

  • 类命名规范

类采用UpperCamelCase命名风格,一些特殊的缩写可以采用全大写,如XML。类命名使用名词描述类的作用。

  • 接口命名规范

接口采用UpperCamelCase命名风格,由于接口定义的是一类功能或动作,所以接口的命名一般使用形容词或动词描述接口的行为。

  • 抽象类命名规范

抽象类除了满足UpperCamelCase风格外,一般需要加上Abstract前缀。

  • 异常类命名规范

异常类除了满足UpperCamelCase风格外,一般需要加上Exception或Error后缀,使用名词描述什么异常或错误。

  • 枚举类命名规范

枚举类除了满足UpperCamelCase风格外,一般加上Enum后缀,枚举类中的枚举值采用全部大写风格,单词与单词之间使用"_"进行分割。

  • 方法命名规范

方法命名采用lowerCamelCase风格,一般使用动词+名词来命名,比较常见的有doXxx,handleXxx,findXxxx。

  • 变量命名规范

变量命名采用lowerCamelCase风格,一般使用名词描述变量的作用,需要注意的是区别于常量,尽量不要使用特殊符号前缀或使用"_"分割符号。

  • 常量命名规范

常量命名采用全部大写,单词与单词之间使用"_"进行分割。

1.2 代码风格

在日常的项目开发中,一个项目有很多人协同开发,每个人使用的开发工具不一样,比如大家常用的vs code和idea,不同的开发工具或代码习惯也会导致代码风格不一致,我们在开发时可能习惯性的进行代码格式化,就会导致整个类改动非常多,在代码合并时容易冲突。

我们可以在项目中增加.editorconfig文件来统一代码风格。

root = true
[*.{adoc,bat,groovy,html,java,js,jsp,kt,kts,md,properties,py,rb,sh,sql,svg,txt,xml,xsd}]charset = utf-8
[*.{groovy,java,kt,kts,xml,xsd}]indent_style = tab              #tab键缩进,可选"space"、"tab"indent_size = 4                 #缩进空格为4个end_of_line = lf                #结尾换行符,可选"lf"、"cr"、"crlf"charset = utf-8                 #文件编码trim_trailing_whitespace = true #不保留行末的空格insert_final_newline = true     #文件末尾增加一个空行curly_bracket_next_line = false #大括号不另起一行spaces_around_operators = true  #运算符两边都有空格indent_brace_style = 1tbs       #条件语句格式是1tbs

1.3 注释规约

类注释

类注释采用/**......*/,在每个类的头部要有必要的注释信息,包括:作者、创建时间、类功能描述

/** * 简单分流算法实验, 每次分流只用考虑当前的桶, 不用回溯历史版本 * {@link https://duapp.yuque.com/team_tech/confluence-data-iwskfg/dzmogk} * @author hufei * @date 2021/6/8 7:59 下午 */

接口注释

接口注释采用/**......*/,在满足类注释的基础上,接口注释应该包含接口的目的、如何使用。

/** * AB分桶算法接口规范 * 对外暴露实验桶计算接口,该接口有一个抽象实现类AbstractBucketAlgorithm,具体的分桶算法实现这个抽象类 * @author hufei * @date 2021/6/8 6:06 下午 */

方法注释

方法注释采用/**......*/,描述方法的功能、输入、输出及返回值说明

/** * 计算实验层的桶信息 * @param layerId 分层 id * @param expId 实验 Id * @param expRatio 新的实验占层流量比例 * @param existsLayerBucket 老的层流量实验配比 * @return 新的层流量实验配比 * @throws BucketAlgorithmException */

方法内部注释

代码做了些什么以及为什么这样做,特别是复杂的逻辑处理部分,要尽可能的给出详细的注释。

全局变量注释

包括变量的功能、取值范围、注意事项等的说明。

/** * 代表kafka收到消息的答复数,0就是不要答复,爱收到没收到.1就是有一个leader broker答复就行,all是所有broker都要收到才行 * 0: Producer不等待kafka服务器的答复,消息立刻发往socket buffer,这种方式不能保证kafka收到消息,设置成这个值的时候retries参数就失效了,因为producer不知道kafka收没收到消息,所以所谓的重试就没有意义了,发送返回值的offset全默认是-1. * 1: 等待leader记录数据到broker本地log即可.不等待leader同步到其他followers,那么假如此时刚好leader收到消息并答复后,leader突然挂了,其他fowller还没来得及复制消息呢,那么这条消息就会丢失了. * all:等待所有broker记录消息.保证消息不会丢失(只要从节点没全挂),这种方式是最高可用的 acks默认值是1. */private String acks = "0";

局部变量注释

主要的局部变量必须有注释,无特别意义的情况下可以不加注释。

2、 可靠性

2.1 增强健壮性

慎用递归算法

递归算法写起来很简单,但用的不好容易导致堆栈溢出和死循环问题。因此尽量不要使用递归算法,如果要使用需要注意以下几个问题:

  1. 把退出条件放在函数最上方,这样比较清晰,防止程序一直不满足退出条件而导致堆栈溢出
public int recursiveAlgorithm(){    if (退出条件)        return 0;    ......}
  1. 避免在递归函数中出现过大的局部变量,这会加速堆栈空间的消耗
public int recursiveAlgorithm(){    char buf[] = new char[1024];}
  1. 增加一个最大递归深度,防止出现死循环导致堆栈溢出

使用参数校验

项目中有很多接口提供给前端,有一些参数在后续的逻辑处理中不能为空并且没有做非空校验,靠口头的约定参数不能为空,如果前端调用时没有传参就会出现空指针异常。最好的解决办法是统一参数校验框架,在接口的入参进行非空限制,如果为空,统一抛出异常。

@Valids({        @Valid(names = "request.expStatus",required = true,regex = "[1,2]",error = "实验状态必须为1或者2"),        @Valid(names = "request.weekDateList,request.type" , required = true)})

幂等校验

我们的系统应该做好幂等校验,等于同一笔业务操作,不管调用多少次,得到的结果都是一样的。比如用户重复下单、MQ消息重复消费、前端重复提交表单等,这些都是需要保证幂等操作。

2.2 善始善终

异常处理

我们在进行异常处理时,一定要把必须执行的语句放到finally块中,比如读写文件时,在finally块中关闭IO连接。

BufferedReader br = null;try {    br = new BufferedReader(new FileReader(new File("")));} catch (FileNotFoundException e) {    e.printStackTrace();} finally {    if (br != null) {        try {            br.close();        } catch (IOException e) {            e.printStackTrace();        }    }}

加锁释放锁

我们在日常的开发中经常会碰到资源竞争的问题,此时我们需要对竞争的资源进行加锁处理,如果我们忘记释放锁,就会导致其他的请求阻塞。

Lock lock = new ReentrantLock();lock.lock();......lock.unlock();

资源释放

我们在日常的开发中会用到数据库或网络资源,此时我们需要建立数据库连接或网络连接,如果我们忘记释放连接就会导致数据库资源或网络资源一直被占用,直到连接失效。虽然我们在日常的开发中不会直接创建连接而是使用连接池,但是连接数最大连接数如果设置的过大,也会导致资源的耗尽。

ResultSet resultSet = null;Statement statement = null;Connection connection = null;try {    Class.forName("com.mysql.cj.jdbc.Driver");    connection = DriverManager.getConnection(url,user,pwd);    statement = connection.createStatement();    resultSet = statement.executeQuery("select * from student");} catch (SQLException e) {    e.printStackTrace();} catch (ClassNotFoundException e) {    e.printStackTrace();} finally {    try {        if (resultSet != null) {            resultSet.close();        }    } catch (SQLException e) {        log.error(e.getMessage(),e);    }    try {        if (statement != null) {            statement.close();        }    } catch (SQLException e){        log.error(e.getMessage(),e);    }    try {        if (connection != null) {            connection.close();        }    } catch (SQLException e){        log.error(e.getMessage(),e);    }}

2.3 异常处理

尽量使用非受检异常

受检异常的缺陷:

  • 受检异常使接口声明脆弱

例如我们定义一个接口

interface User {    public void changePassword() throws MySecurityException;}

随着业务的开发,接口抛出的异常增加,比如新增一个RejectChangeException,那就需要修改User接口,这就会导致所有的User接口调用者都要追加对RejectChangeException异常的处理。

  • 受检异常使代码的可读性降低

一个方法增加了受检异常,则调用者必须对异常进行处理,比如调用无受检异常:

public static void main(String[] args) {    userImpl.changePassword();}

如果调用受检异常就不一样了:

public static void main(String[] args) {    try {        userImpl.changePassword();    } catch(Exception e){        e.printStackTrace();    }}

如果一个方法使用了受检异常,那么调用者就必须处理,特别是在多个异常的情况下,要增加多个catch块进行处理,就会增加代码复杂度。

  • 受检异常增加了开发工作量

不要在finally块中处理返回值

在finally块中进行return会导致如下问题:

  • 覆盖了try代码块中的return返回值,比如下面的方法,我们传入100返回结果也是-1。
public static void main(String[] args) {    calculate(100);}public static int calculate(int number) throws Exception {    try {        if (number < 100) {            throw new DataFormatException("数据格式错误");        } else {            return number;        }    } catch (Exception e) {        throw e;    } finally {        return -1;    }}
  • 屏蔽异常

当我们在try块中抛出异常时,异常线程在监视到有异常发生时,就会在异常表中登记当前的异常类型为DataFormatException,但是当执行器执行finally代码块时,就会重新为方法赋值,也就是告诉调用者"该方法执行正确,没有异常产生",比如我们调用上面的方法,传入-1,不会抛出异常。

异常封装

Java中提供了异常处理机制,可以保证程序的健壮性,但是Java中提供的都是通用异常,我们在项目的开发中需要封装一些业务的异常。

统一异常处理

在项目开发中,可以用切面统一异常处理或依赖SpringMVC的ControllerAdvice,将错误信息按项目统一格式返回给前端,这样在开发过程中只用抛出异常就可以了。

禁止直接吞掉异常

吞掉异常会导致难以排查程序运行过程中出现的问题,应该将异常向上抛出。

2.4 留意编译告警

程序中的编译告警容易被忽略,因为即使出现了告警,源文件仍能被编辑通过并运行,尤其是我们在开发的过程中使用IDE,但这些告警中往往隐藏着一些潜在的问题。

2.5 尽早暴露问题

一个bug在项目的开发、自测、测试、发布等阶段被发现,其修复成本是不一样的,越往后修复成本越高,尤其到了线上可能还会造成一定的资损。项目研发在开发和自测的过程中,应重视代码质量,自测或使用bug扫描工具来尽早的发现问题。

SpotBugs

SpotBugs提供静态字节代码分析,它使用静态分析来查找400多种错误模式,例如空指针取消引用、无限递归循环,对Java库的错误使用和死锁。

3、 可维护性

3.1 记录日志

  • 所有后台都要有操作日志、数据变更日志

  • 日志要配置异步写盘

  • 线上仅保留WARN和ERROR级别日志

  • 所有日志都要有traceId

  • 异常日志要有堆栈、入参、能说清楚是什么错误的信息

  • 打印日志时,禁止直接用JSON工具将对象转换成String

3.2 明确错误提示

在产品的使用中,我们会提示一些错误信息给用户,但是如果提供笼统的错误提示可能令用户感到困惑,如:"服务暂不可用",尤其我们不能提示:"系统内部错误,请联系系统管理员!"给用户,这会降低用户对产品的信任度。我们可以提示具体的错误信息,如"xxx信息未填,请先填写完成。"

3.3 保持代码简洁性

避免嵌套if/else

代码中经常会进行空值判断和逻辑判断,if/else嵌套会使得代码逻辑看起来非常复杂。

public  static String getDepartmentNameOfUser(String username) {    Result<User> result = getUserByName(username);    if (result != null) {        User user = result.getData();        if (user != null) {            Department department = user.getDepartment();            if (department != null) {                return department.getName();            }        }    }    return "未知部门";}

尽量避免嵌套if/else,可以这样写:

public static String getDepartmentNameOfUser(String username) {    Result<User> result = getUserByName(username);    if (result == null) {        return "未知部门";    }    User user = result.getData();    if (user == null) {        return "未知部门";    }    Department department = user.getDepartment();    if (department == null) {        return "Department为空";    }    return department.getName();}

抽取类、方法

让类或方法的职责更加明确,不要把所有的逻辑写到一个方法里面。

public boolean addExp(ExpVO expVO){    //校验参数是否正确,如果失败直接抛出异常    checkParamValidate(expVO);    //新增实验信息    addExp(expVO);    //新增实验组    expGroupService.addExpGroup(expVO);    //新增实验层    expLayerService.addExpLayer(expVO);    //计算实验流量    List<ObjectFLowEntity> flowEntityList = flowService.calculateFlow(expVO);    //保存流量信息    flowService.saveFlow(flowEntityList);}

不要使用魔法值

不要在代码中使用魔法值,这样后续如果值变化改动起来会漏掉。

public boolean addExp(){    //服务端实验    expEntity.setExpType(1);}

可以使用枚举:

public boolean addExp(){    expEntity.setExpType(ExpTypeEnum.SERVER.getExpType());}

3.4 使用开源工具

使用一些开源工具可以减少我们重复造轮子,而且常用的开源工具都有完整的单元测试覆盖,可以有效的减少bug的出现。

Google Guava

Guava是一组来自Google的核心Java库,其中包括集合、缓存、原生类型、并发、常用注解、基本字符串操作和I/O等等。

比如集合的交、并、查集,使用Google Guava就很方便。

Set<Integer> sets = Sets.newHashSet(1, 2, 3, 4, 5, 6);Set<Integer> sets2 = Sets.newHashSet(3, 4, 5, 6, 7, 8, 9);//交集SetView<Integer> intersection = Sets.intersection(sets, sets2);//差集SetView<Integer> diff = Sets.difference(sets, sets2);//并集SetView<Integer> union = Sets.union(sets, sets2);

Apache Commons

Apache Commons是对JDK的扩展,包含了很多开源的工具,下面是我们项目中常用的工具:

Commons Lang3:处理Java基本对象方法的工具类,提供对字符、数组等基本对象的操作。

Commons Codec:提供常用的编码和解码方法,如DES、SHA1、Base64。

Commons BeanUtils:提供Bean的动态生成。

Commons HttpClient:简化HTTP客户端与服务端的各种通讯。

Log4j

各大开源框架和项目中用的最多的日志框架。

4、 可扩展性

我们写的代码都是为了特定的需求服务的,但是这些需求并不是一成不变的,当需求变更了,如果我们代码的扩展性很好,我们可能只需要简单的添加或者删除模块就行了,如果扩展性不好,可能所有的代码都需要重写,所以提供代码的扩展性是必须的,我们在写代码的时候使用设计模式可以使代码具备很好的扩展性。

比如AB分流算法,分流算法根据不同的场景不同的需求有不同的实现,我们定义好算法的接口,不同的分流算法实现这个接口,那么我们在使用的时候只需要考虑使用哪个算法就行了,不需要关心算法的实现。

/** * AB分桶算法 * @author hufei * @date 2021/6/8 6:06 下午 */public interface BucketAlgorithmTemplate {
    /**     * ab分桶算法     *     * @param layerId     * @param expId        实验id     * @param expRatio     实验占层流量比     * @param expGroups    实验组详情     * @param existsBucket 已存在的实验层和实验组流量详情,第一次创建传null     * @return     * @throws BucketAlgorithmException     */    public Map<Integer, JSONObject> calculateBucket(Integer layerId, Integer expId, Integer expRatio, Map<Integer, Integer> expGroups, Map<Integer, List<JSONObject>> existsBucket) throws BucketAlgorithmException;}

/** * AB分桶算法 * @author hufei * @date 2021/6/8 6:39 下午 */public abstract class AbstractBucketAlgorithm implements BucketAlgorithmTemplate {    /**     * @param expId        实验id     * @param expRatio     实验占层流量比     * @param expGroups    实验组详情     * @param existsBucket 已存在的实验层和实验组流量详情,第一次创建传null     * @return     * @author hufei     * @description ab分桶算法     * @date 2021/6/8 6:31 下午     */    public Map<Integer, JSONObject> calculateBucket(Integer layerId, Integer expId, Integer expRatio, Map<Integer, Integer> expGroups, Map<Integer, List<JSONObject>> existsBucket) throws BucketAlgorithmException {        calculateVerify();        Map<Integer, JSONObject> bucketMap = new HashMap<>();        JSONObject layerObj = new JSONObject();        JSONObject expObj = new JSONObject();        if (existsBucket == null) {            //如果不存在历史分流信息,说明层是新建,实验也是新建            layerObj = newLayer(layerId, expId, expRatio);            expObj = newExp(expGroups);        } else if (existsBucket.get(layerId) != null && existsBucket.size() == 1) {            //只有层的历史记录,但是没有实验的历史记录,说明层已经存在实验是新建            layerObj = calculateLayer(layerId, expId, expRatio, existsBucket.get(layerId));            expObj = newExp(expGroups);        } else if (existsBucket.get(layerId) != null && existsBucket.get(expId) != null) {            //有层和实验的历史记录,说明层不是新建并且实验也不是新建            layerObj = calculateLayer(layerId, expId, expRatio, existsBucket.get(layerId));            expObj = calculateExp(expGroups, existsBucket.get(expId));        }        bucketMap.put(layerId, layerObj);        bucketMap.put(expId, expObj);        return bucketMap;    }    /**     * @param layerId     * @param expId     * @param expRatio     * @return     * @author hufei     * @description 新建层     * @date 2021/6/10 11:24 上午     */    public JSONObject newLayer(Integer layerId, Integer expId, Integer expRatio) {        return new JSONObject();    }    public JSONObject calculateLayer(Integer layerId, Integer expId, Integer expRatio, List<JSONObject> layerHistoryFlow) {        return new JSONObject();    }    /**     * @return     * @author hufei     * @description 新建实验     * @date 2021/6/10 11:24 上午     */    public JSONObject newExp(Map<Integer, Integer> expGroups) {        return new JSONObject();    }
    public JSONObject calculateExp(Map<Integer, Integer> expGroups, List<JSONObject> expHistoryFlow) {        return new JSONObject();    }}

/** * 简单分流算法实验, 每次分流只用考虑当前的桶, 不用回溯历史版本 * {@link https://duapp.yuque.com/team_tech/confluence-data-iwskfg/dzmogk} * @author hufei * @date 2021/6/8 7:59 下午 */public class SimpleBucketAlgorithm extends AbstractBucketAlgorithm {    /**     * @param layerId     * @param expId     * @param expRatio     * @return     * @author hufei     * @description 新建层     * @date 2021/6/10 11:24 上午     */    @Override    public JSONObject newLayer(Integer layerId, Integer expId, Integer expRatio) {        ......    }    @Override    public JSONObject calculateLayer(Integer layerId, Integer expId, Integer expRatio, List<JSONObject> layerHistoryFlow) {        ......    }    /**     * @return     * @author hufei     * @description 新建实验     * @date 2021/6/10 11:24 上午     */    @Override    public JSONObject newExp(Map<Integer, Integer> expGroups) {        ......    }    /**     * 实验组流量变更:     * 1.优先从右边空白开始分配     * 2.先增后减     *     * @param expGroups     * @param expHistoryFlow     * @return     */    @Override    public JSONObject calculateExp(Map<Integer, Integer> expGroups, List<JSONObject> expHistoryFlow) {        .......    }    /**     * 递归计算     */    private void calculateExpRecursion(JSONObject currentExpFlow, Map<Integer, Long> groupCountMap, List<Integer> positiveRatioList, List<Integer> negativeRatioList, Map<Integer, Long> groupNeedAddOrReduceRatioMap) {            }}

5、 效率

5.1 代码优化

循环优化

for (int i=0;i<list.size;i++) {...}for (int i=0,size=list.size();i<size;i++){...}

不要在循环中创建对象

集合优化

在初始化集合时,尽量指定可预知的集合大小,减少集合的扩容次数。

5.2 引入并发

并发可以很好的提升程序的执行时间,但是使用不好也会带来很多问题。如果任务和任务之间没有关联性,我们并发的执行任务来缩短整体时间。

CompletableFuture[] cfs = tailorEntry.getValue().values().stream().map(layerExtraInfo -> CompletableFuture.supplyAsync(() -> layerCalculate(layerExtraInfo, userHitWhiteListMap, request.getUserId(), needGroupId, request.getCurrentGroupParm()), asyncFlowServiceExecutor).whenComplete((r, e) -> {    if (!r.isEmpty()) {        hitGroupList.addAll(r);        r.forEach(g -> {            needGroupId.add(g.getId());        });    }})).toArray(CompletableFuture[]::new);CompletableFuture.allOf(cfs).join();

提高代码质量是一个复杂且持续的工作,一篇文章的讲解也很有限,我们在项目的开发中需要持续不断的迭代优化,来保证代码的质量。

标签:return,代码,param,如何,质量,Integer,null,异常,public
From: https://www.cnblogs.com/lmg168/p/17408887.html

相关文章

  • 1.图片去雾MATLAB代码 图像去雾技术 包括全局及局部直方图均衡化
    1.图片去雾MATLAB代码图像去雾技术包括全局及局部直方图均衡化和基于RETINEX理论去雾三种算法局部直方图和retinex(MSR)的参数,懂算法可以自己调节图像亮度含GUI界面只有代码。ID:495615931294745......
  • MATLAB图像增强代码 代码些许复杂,由本人一个朋友编写 是
    MATLAB图像增强代码代码些许复杂,由本人一个朋友编写是机器视觉和图像增强领域的应用,有gui界面,可以载入原图和参照强化的图像,读取参照图像的RGB或者HSV分量,并强化原图像,运行,corrction.m.结果如下图ID:925620010658083......
  • docker-compose部署sonarqube开源代码审计和分析平台
    mkdir-p/docker/sonar/postgres/postgresqlmkdir-p/docker/sonar/postgres/datamkdir-p/docker/sonar/sonarqubechmod777-R/docker/sonar/sonarqubeecho"vm.max_map_count=262144">/etc/sysctl.confsysctl-pversion:'3'servic......
  • 匠心精神--来看一个小迭代的代码实现
    问题我司对外部商户提供的API中,有一个年久失修的开票记录查询接口,近期在一次集中测试时,发现这个接口的响应值与接口文档里描述的不一致。代码里定义的field名是type,而文档里参数名是invoiceTypeId。 修改方案因为无法确定原先的type有没有商户在用,所以,在模型类里新增invoiceT......
  • 如何搭建一个vue项目
    一、nvm安装与使用1.1、nvm简介nvm全名node.jsversionmanagement,顾名思义是一个nodejs的版本管理工具。通过它可以安装和切换不同版本的nodejs1.2、nvm下载①github下载https://github.com/coreybutler/nvm-windows/releases②百度网盘下载链接:https://pan.baidu.com/s/1......
  • dw代码跳转
    <html><style type="text/css">img{width: 200px;height: 200px;}</style><script language="JavaScript" type="text/javascript">function fn(n){var nextn;if(n==3){nextn=1;}else{nextn=n+1;}document......
  • 如何使用Go与MQTT进行通信
    简介本文介绍了如何使用Go编程语言与MQTT(MessageQueuingTelemetryTransport)进行通信。MQTT是一种轻量级的消息传输协议,广泛应用于物联网和实时通信场景。通过本文的指导,您将学习如何使用Go语言创建MQTT客户端,进行消息的发布和订阅。准备工作在开始之前,请确保您已完......
  • C++用代码验证“一切函数皆可傅里叶”
    #include<stdio.h>#include<math.h>#definepi3.1415926#definerows3#definecolums5typedefstruct{floatre;//reallyfloatim;//imaginary}complex,*pcomplex;complexcomplexadd(complexa,complexb)//复数加{comple......
  • 如何调用api接口获取其中的数据
    ​ part1.API接口可以运用到的场景,主要包括以下几个方面:1. 应用程序集成:API可以使不同的应用程序相互之间进行集成,比如将某个应用程序的数据传递给另一个应用程序,或者调用另一个应用程序的功能。2. 数据共享:API可以用来共享数据,从而使不同的应用程序可以访问同一组数据......
  • 48024 python 代码分析
    ComputerScience220SC(2023)Assignment3(Basicgraphalgorithmsandtraversals)SeeCanvasforduedateThisassignmentrequiresyoutosubmitprogramsinPythonthatyouhavewrittenyourselftotheautomarker,https://www.automarker.cs.auckland.ac.nz.Yo......