Day961.老城区前端改造 -遗留系统现代化实战

news2024/11/24 13:09:20

老城区前端改造

Hi,我是阿昌,今天学习记录的是关于老城区前端改造的内容。

什么是“`改造老城区”。改造老城区模式 是指对遗留系统内部的模块进行治理,让模块内部结构合理、模块之间职责清晰的一系列模式。

也就是说,在遗留系统的单体内部,应该如何更好地治理架构。

按照从“前”往“后”的顺序,先从前端开始。


一、遗留系统的前端

一种架构反模式——Smart UI,它是遗留系统最常见的前端模式。

以 Java Web 项目为例,它们往往在 JSP 页面中掺杂着大量的 JavaScript、Java 和 HTML 代码。其中最致命的就是 Java 代码,因为它们可以随意访问后端的代码,甚至访问数据库。重构前端代码最主要的工作,就是移除这些 Java 代码。前端的遗留代码和后端的遗留代码一样,也是坏味道的重灾区。

Martin Fowler 在《重构(第 2 版)》中,用 JavaScript 重写了所有代码示例,这对于前端开发人员是相当友好的。它能帮助你别出 JavaScript 中的坏味道,并重构这些代码。

要重构前端代码,最好也要优先添加测试。但不幸的是,已有的前端测试工具对基于框架(Angular、React、Vue)的 JavaScript 代码是相对友好的,但遗留系统中的前端代码,既有 JavaScript 又有 Java,很难用前端工具去编写单元测试。

这里推荐编写一些 E2E 测试,来覆盖端到端的场景。或者使用HtmlUnit 这样的工具,通过编写 Java 代码来测试 JSP。但实际上 HtmlUnit 也属于某种程度的 E2E 测试。


二、重构前端代码

前端 JSP 代码的重构和后端有相似之处,但也有很多不同。一套端(前端 JSP)到端(后端 Java API)的遗留 JSP改造方案,包括重构前后的代码对比。

对于遗留 JSP 代码的重构,可以分成以下八个步骤。

每个步骤都可以小步迭代增量演进


1、梳理业务

要想重构前端代码,必须先搞懂它的含义。

类似代码现代化时,用活文档工具去理清一个场景,要重构前端代码,也得先梳理它的业务含义,搞清楚它到底做了哪些事情。

遗憾的是,针对前端的活文档工具,现在还没发现哪种比较好,因为前端有多种语言交织在一起,分析起来太麻烦。

不过好在前端并不像后端代码那样调用链很深,很多前端代码都是围绕一个页面来展开的,相对来说还算内聚,梳理起来也更容易一些。


2、模块化

模块化是指,按职责把原先冗长的 JSP 页面拆分出来,分解成多个小的 JSP 页面,比如 header、footer、content 等,并将它们 include 到大页面中。开发人员在编写 JSP 时,很少有这种模块化的思想,导致所有的东西都写到一个文件里。

随着页面逻辑越来越复杂,页面里的各种代码越来越多,文件也越来越大。甚至见过很多超出 64KB 限制的 JSP 文件。

模块化怎么实现,结合例子来分析分析。从下面这段 to-do list 的代码示例,可以很明显地看出它由 4 个部分组成:

一个包含若干 hidden 字段的 form、一个包含一段文字的 header、一个包含 to-do 列表的 section 和一个包含删除按钮的 footer:

<% List<Todo> todoList = (List<Todo>) request.getAttribute("todoList"); %>
<section class="todoapp">
    <form name="todoForm" action="" method="post">
        <input type="hidden" name="sAction"/>
        <input type="hidden" name="title"/>
        <input type="hidden" name="id"/>
    </form>
    <header class="header">
        <h1>todos</h1>
        <input class="new-todo" placeholder="What needs to be done?" autofocus>
    </header>
    <section class="main">
        <input id="toggle-all" class="toggle-all" type="checkbox">
        <label for="toggle-all">Mark all as complete</label>
        <ul class="todo-list">
            <% for (int i = 0; i < todoList.size(); i++) {
                Todo todo = todoList.get(i);
            %>
            <li <%if (todo.getCompleted()) {%> class="completed"<%}%> data-id="<%=todo.getId()%>">
                <div class="view" id="todo_<%=todo.getId()%>">
                    <input class="toggle" id="todo_toggle_<%=todo.getId()%>" onchange="toogle(this)"
                           type="checkbox" <%if (todo.getCompleted()) {%> checked <%}%> />
                    <label><%=todo.getTitle()%>
                    </label>
                    <button class="destroy" onclick="deleteTodo(this)"></button>
                </div>
                <%--            <input class="edit" value="<%=todo.getTitle()%>">--%>
            </li>
            <%}%>
        </ul>
    </section>
    <footer class="footer">
        <%
            boolean hasCompleted = false;
            for (Todo todo : todoList) {
                if (todo.getCompleted()) {
                    hasCompleted = true;
                    break;
                }
            }
        %>
        <%if(hasCompleted) {%>
           <button class="clear-completed" onclick="deleteCompletedTodo()">Clear completed</button>
        <%}%>
    </footer>
</section>

对于这段代码,就可以将它提取成 4 个小的 JSP 页面,重构之后的代码就相当清爽了。

<section class="todoapp">
    <%@ include file="todoForm.jspf" %>
    <jsp:include page="todoHeader.jsp"/>
    <jsp:include page="todoList.jsp"/>
    <jsp:include page="todoFooter.jsp" />
</section>

3、重构 JSP 中的 JavaScript 代码

将页面拆小后,还要继续重构各个小页面中的 JavaScript 代码。

长函数是遗留系统前端最常见的坏味道。

早年开发 JSP 页面的都不是专业的前端开发,很多都是赶鸭子上架的后端开发人员,去写一些相对简单的 JavaScript 代码。

或者是不知道怎么写某个页面元素的联动效果,就去论坛复制一大堆代码过来,稍加修改,只要代码能跑就敢提交,很少会清理和重构代码。这些操作导致业务逻辑、显示逻辑和控件操作杂糅在一起,根本没法维护。面对这种混乱的代码,更要确保思路清晰。

可以按职责来将它分解为若干个小的函数,每个函数只做一件事情。这有点类似于代码重构中的拆分阶段(可以回顾重构“烂代码”),只不过拆分出来的是不同的职责,而不是一个职责的不同阶段。

下面这段代码是一个页面校验的函数,大体业务是,某家公司在组织团建时需要选择一个团建活动,而不同的团建活动之间有一些校验逻辑。

具体的说明可以参考这里。

function clickActivityCheck(activityCheckedObject, packageId, activityId) {
    if (activityCheckedObject.checked) {
        var countInput = document.getElementById("activity_" + activityId + "_count");
        var count = !countInput.value ? -1 : parseInt(countInput.value);
        if (count < 1 || count > 50) {
            alert("参加人数必须在1到50之间!");
            activityCheckedObject.checked = false;
            return;
        }
        if (activityId === 1) {
            var checkBox2 = document.getElementById("activity_2");
            if (checkBox2 && checkBox2.checked) {
                alert("冬奥两日游和户外探险一日游不能同时选择!");
                activityCheckedObject.checked = false;
                return;
            }
        }
        if (activityId === 2) {
            var checkBox1 = document.getElementById("activity_1");
            if (checkBox1 && checkBox1.checked) {
                alert("户外探险一日游和冬奥两日游不能同时选择!");
                activityCheckedObject.checked = false;
                return;
            }
        }
        if (activityId === 5) {
            var checkBox1 = document.getElementById("activity_1");
            if (!checkBox1 || !checkBox1.checked) {
                alert("选择住宿前必须选择冬奥两日游!");
                activityCheckedObject.checked = false;
                return;
            }
        }
        var result = createActivity(activityCheckedObject, packageId, activityId);
        if (!result.success) {
            alert(result.errorMessage);
            activityCheckedObject.checked = false;
            return;
        }
    } else {
        var countInput = document.getElementById("activity_" + activityId + "_count");
        countInput.value = "";
        activityCheckedObject.checked = false;
        if (activityId === 1) {
            var checkBox5 = document.getElementById("activity_5");
            if (checkBox5 && checkBox5.checked) {
                checkBox5.click();
            }
        }
        cancelActivity(activityCheckedObject, packageId, activityId);
    }

这段代码有点长,不过稍微解释一下就清楚了

。在选择团建活动的时候,用户可以在“冬奥两日游”、“户外探险一日游”、“唱歌”、“吃饭”、“住宿”等活动中做出选择。

在勾选 checkbox 的时候,会触发这个函数来进行校验。

它首先会校验所填的人数,然后校验所选活动之间的关系,比如冬奥和户外探险不能同时选择,选住宿则必须选冬奥。

此外,当取消勾选的时候,也会触发一个联动逻辑,也就是取消冬奥的时候,会连带着一起取消住宿。

题面分析完了,想到重构的思路了么?脑海里涌现的第一个想法可能是这样的:

可以将每个 if 都抽取成函数。但这样抽取出来的函数仍然有很多重复代码,逻辑并没有得到简化,而且页面元素的读写和业务判断还是混杂在一起的。

仔细观察你会发现,所有的校验逻辑可以大体上分为 3 种:

  • 校验人数
  • 校验互斥的活动
  • 校验有依赖的活动

对校验逻辑做了抽象之后,就可以把代码重构为下面这个样子:

function validateActivities(activityId) {
    var result = validateCount(activityId);
    if (result.success) {
        result = validateMutexActivities(activityId);
        if (result.success) {
            result = validateReliedActivities(activityId);
        }
    }
    return result;
}

function selectActivity(activityId) {
    var result = validateActivities(activityId);
    if (result.success) {
        result = createActivity(activityId);
    }
    return result;
}

function clickActivityCheck(activityId) {
    let activityInfoRow = new ActivityInfoRow(activityId);
    if (activityInfoRow.isChecked()) {
        var result = selectActivity(activityId);
        if (!result.success) {
            alert(result.errorMessage);
            activityInfoRow.setChecked(false);
        }
    } else {
        unSelectActivity(activityInfoRow, activityId);
    }
}

注意,这里面还提取了一个 ActivityInRow 对象,用于保存每一行的活动元素。

这样,就把页面元素和判断逻辑分离出来了。


4、移除 JSP 中的 Java 代码

JSP 中,用 <% %> 括起来的 Java 代码叫做 Scriptlet,正是这样的代码,把 JSP 变成了 Smart UI。

其实早在 JSTL 和 EL 诞生的时候,就不再推荐使用 Scriptlet 了。

然而十几年来,情况不曾改观,反倒是新型前端框架的兴起,使前后端彻底分离,才遏制住了 Scriptlet 的滥用之势。但对于遗留系统来说,Scriptlet 仍然泛滥成灾,重构前端代码的重点,就是移除它们。

JSP 中的 Scriptlet 大致可以分为这么几类:

  1. 对所有请求执行相同的 Java 代码,如权限验证。这类 Scriptlet 可以迁移到后端,写到一个 Filter 里。
  2. 直接与数据库交互的 Java 代码,如从数据库中查询出数据并显示在 table 中,或登录页面中验证用户名和密码等。这类 Java 代码其实处理的都是 GET/POST 请求,也可以迁移到后端,实现一个新的 Servlet,将代码迁移到 doGet/doPost 中。
  3. 控制页面显示逻辑的 Java 代码,如上面 to-do 的例子中,从后端拿到一个 Todo 对象的列表,然后遍历这个列表,用

便签展示出来。对于第三种 Java 代码,可以用 JSTL 和 EL 来替换,就像下面这样:

<section class="main">
    <ul>
        <c:forEach var="todoItem" items="${todoList}">
            <li>${todoItem.title}</li>
        </c:forEach>
    </ul>
</section>

完成替换后,JSP 中就只剩下了 HTML、JavaScript 和 JSTL,已经相当清爽了。

如果工作就只是移除 Java,那么到此就可以告一段落。

但如果目标是前后端分离,彻底告别 JSP,可能会希望使用纯 JavaScript 来替换。这时候就可以先保留这部分 Scriptlet,等下一步引入前端框架的时候,再来替换。


5、引入前端框架

当 Java 代码移除之后,再引入前端框架。

比如对于 todoList 这个模块,引入 Vue 后的代码就变成了下面这样:

<%
    List<Todo> todoList = (List<Todo>) request.getAttribute("todoList");
    ObjectMapper objectMapper = new ObjectMapper();
    String todoListString = objectMapper.writeValueAsString(todoList);
%>
<div id="todoListContainer"></div>
<script>
    (function () {
        var todos = JSON.parse('<%=todoListString%>');
        new Vue({
            el: "#todoListContainer",
            data: function () {
                return {
                    todos
                }
            },
            template:`
<section class="main" v-show="todos.length">
    <ul class="todo-list">
        <li v-for="todo in todos" :key="todo.id" :class="{completed: todo.completed}">
            <div class="view">
                <input class="toggle" v-model="todo.completed" type="checkbox" @change="toggleComplted(todo)"/>
                <label>{{todo.title}}</label>
                <button class="destroy" @click="deleteTodo(todo)"></button>
            </div>
        </li>
    </ul>
</section>
            `,
            methods: {
                toggleComplted: function (todo) {
                    var sAction = "markDone";
                    if (!todo.completed) {
                        sAction = "markUnfinished";
                    }
                    rootPage.toggleTodo(todo.id, sAction);
                },
                deleteTodo: function (todo) {
                    rootPage.deleteTodo(todo.id);
                }
            }
        });
    })();
</script>

注意,这里只是使用脚本的方式引入了 Vue。

要想更好地使用前端框架,还需要对这些代码进行组件化和工程化。


6、前端组件化

引入前端框架之后,就可以进一步重构,将前面拆分出来的各个模块转换为组件。

比如上面的 Vue 可以转换为下面这样的组件:

var todoListComponent = {
    props:{
        todos: {
            type: Array
        }
    },
    template:`
<section class="main" v-show="todos.length">
    <ul class="todo-list">
        <li v-for="todo in todos" :key="todo.id" :class="{completed: todo.completed}">
            <div class="view">
                <input class="toggle" v-model="todo.completed" type="checkbox" @change="toggleComplted(todo)"/>
                <label>{{todo.title}}</label>
                <button class="destroy" @click="deleteTodo(todo)"></button>
            </div>
        </li>
    </ul>
</section>
            `,
    methods: {
        toggleComplted: function (todo) {
            var sAction = "markDone";
            if (!todo.completed) {
                sAction = "markUnfinished";
            }
            this.$emit("toggle-todo", todo.id, sAction);
        },
        deleteTodo: function (todo) {
            this.$emit("delete-todo", todo.id);
        }
    }
}

在 index.jsp 文件中,就可以使用这种方式来引用这个组件:

<div id="app">
    <todo-list-component :todos="todos" v-on:toggle-todo="toggleCompleted" v-on:delete-todo="deleteTodo" >
    </todo-list-component>
</div>

此时组件的初始化数据还是从 request 中获取的,要把它们替换成对后端的 Ajax 调用

这需要改造一下原有的 Servlet,让原本在 request 中设置 attribute 的 Servlet 返回 json:

ObjectMapper objectMapper = new ObjectMapper();
PrintWriter out = response.getWriter();
response.setContentType("application/json;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
response.setStatus(HttpServletResponse.SC_OK);
List<Todo> todoList = todoRepository.getTodoList();
out.write(objectMapper.writeValueAsString(todoList));
out.flush();

这时,前端页面中的所有 Scriptlet 都清除干净了,可以将文件名的后缀从 jsp 改为 html 了。


7、前端工程化

它们还是通过 <script> 的方式引入到页面中的,只能说是个半成品。要构建一个现代化的前端应用,工程化是必不可少的。

对于上面的例子,引入 Vue CLI,它能更好地管理 Vue 组件。比如之前的 todoList.js,将会变成下面这样的 TodoList.vue:

<template>
  <section class="main" v-show="todos.length">
    <ul class="todo-list">
      <li v-for="todo in todos" :key="todo.id" :class="{completed: todo.completed}">
        <div class="view">
          <input class="toggle" v-model="todo.completed" type="checkbox" @change="toggleComplted(todo)"/>
          <label>{{todo.title}}</label>
          <button class="destroy" @click="deleteTodo(todo)"></button>
        </div>
      </li>
    </ul>
  </section>
</template>
<script>
import $ from 'jquery';
export default {
  name: "TodoList",
  props:{
    todos: {
      type: Array
    }
  },
  methods: {
    toggleComplted: function (todo) {
      console.log("toggleComplted ")
      console.log(todo)
      var sAction = "markDone";
      if (!todo.completed) {
        sAction = "markUnfinished";
      }
      $.ajax({
        url: "/todo-list/ajax?sAction=" + sAction,
        method: 'post',
        data: {
          id: todo.id
        },
        error: function () {
          todo.completed = !todo.completed;
        }
      })
    },
    deleteTodo: function (todo) {
      var _this = this;
      $.ajax({
        url: "/todo-list/ajax?sAction=delete",
        method: 'post',
        data: {
          id: todo.id
        },
        success: function () {
          _this.$emit('delete-todo', todo.id);
        }
      })
    }
  }
}
</script>

第五步到第七步这三步,如果要改造的页面比较多,无法在一个交付周期内改造完成。

就可以采取这种单页应用注入(SPA Injection)的方式来逐步地完成替换。

它与绞杀植物模式的思想类似,都是一种以新替旧的方法。只不过与绞杀植物由外及内(outside-in)的替换方式不同,单页应用注入是由内及外(inside-out)的。它把 SPA 注入到 JSP 内部,先替换一个局部的组件,然后再慢慢扩散,直到替换完全部功能。

通过这种方式,就能让 SPA 和老的 JSP 共存,更有利于增量演进。


8、API 治理

完成了前面的七个步骤,前端的工作基本上告一段落,接下来要治理的就是后端的 API 了。

在第七步已经将部分 Servlet 改造成了对 Ajax 调用友好的 API,但这还不够,可以更进一步,将它们改写为 REST API,并且对后端进行分层,以消除事务脚本。


三、微服务架构下的前端

前端代码的重构是个浩大的工程,持续的时间会很长。

因此通常不建议直接重构遗留系统的所有前端代码,而是以后端的微服务拆分为契机,拆分出来哪些模块,就重构哪些模块的前端

这样重构的范围会更小,拆分出来的服务从前到后也都是现代化的。但这样一来会出现一个问题,老的页面仍然是 JSP 的,而新的页面已经组件化工程化了,如何集成呢?

可以使用微前端(Micro frontend)技术。微服务是把庞大的单体应用分解成小的、认知负载低的服务,从而将“大事化小,小事化了”。

微前端也是同样的思路,它将一个单体的前端应用拆分为多个小型的前端应用,并通过某种方式聚合到一起。

各个前端应用可以独立运行、开发和部署。对于这一部分的内容,推荐你参考黄峰达开源的一套微前端框架Mooa,还有跟它配套套的微前端解决方案文档。


四、总结

遗留系统前端的特点,以及前端重构的八个步骤, 分别是:

梳理业务、模块化、重构 JavaScript、移除 Scriptlet、引入前端框架、前端组件化、前端工程化和 API 治理。

在这里插入图片描述

最后,为了在旧页面中更好地集成新的前端组件,粗略地了解了微前端技术。在重构前端的时候,对开发人员的要求是很高的。

如果他只会前端,可能无法移除 Scriptlet;如果只会后端,也许可以按重构 Java 代码的方式重构 JavaScript,但却很难对前端进行组件化和工程化,更别提什么微前端。

作为架构师或重构负责人的你,这时候需要带领不同能力的人一起攻关,必要的时候可以让他们结对,以发挥不同人的特长。

前端的治理就像是对老城区中的“老破小”小区粉刷了一遍外墙,让它变得好看了一些。但实际上,还是需要好好整治一下内部结构(后端代码),否则就是虚有其表。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/489639.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Redis】聊一下Redis基础架构

我们知道学习一个技术&#xff0c;最好的方式就是从全局观出发&#xff0c;然后针对不同的点进行拆分&#xff0c;一个个破解。既可以将学到的和已有的知识联系起来&#xff0c;又可以有一定的深度和目的性。 Redis基础架构 对于一个中间件来说&#xff0c;一个是使用层面&…

GO数组切片-线性数据结构

数据结构 类型 什么是类型 &#xff1f; 内存中的二进制数据本身没有什么区别&#xff0c;就是一串0或1的组合。 内存中有一个字节内容是0x63&#xff0c;他究竟是深恶 字符串?字符&#xff1f;还是整数&#xff1f; 本来0x63表示数字 但是文字必须编码成为0和1的组合 才能记…

【C++】红黑树源码剖析

目录 概述 算法 调整策略 源码 RBTree.h test.cpp 概述 红黑树&#xff0c;是一种二叉搜索树&#xff0c;每一个节点上有一个存储位表示节点的颜色&#xff0c;可以是Red或Black。 通过对任何一条从根到叶子的路径上各个节点着色方式的限制&#xff0c;红黑树确保没有一…

ChatGPT、文心一言、New Bing到底哪个更AI?

Hello 各位小伙伴&#xff0c;要说今年最火爆的 IP 有哪些&#xff0c;那一定少不了人工智能&#xff08;AI&#xff09;&#xff0c;市面上已经相继出现了我们熟知的 ChatGPT&#xff08;OpenAI&#xff09;、ChatGPT 国外镜像网站、文心一言&#xff08;百度&#xff09;、Ne…

MySQL --- DDL图形化工具表结构操作

一. 图形化工具 1. 介绍 前面我们讲解了DDL中关于数据库操作的SQL语句&#xff0c;在我们编写这些SQL时&#xff0c;都是在命令行当中完成的。大家在练习的时候应该也感受到了&#xff0c;在命令行当中来敲这些SQL语句很不方便&#xff0c;主要的原因有以下 3 点&#xff1a;…

redis的介绍和安装

文章目录 一、redis的介绍和安装1.1 初识redis1.1.1 redis是什么&#xff1f;1.1.2 redis能做什么&#xff1f; 1.2 redis的安装配置 一、redis的介绍和安装 1.1 初识redis 1.1.1 redis是什么&#xff1f; Redis是一个开源的内存数据存储系统&#xff0c;也可以被用作数据库、…

阿里云服务器vCPU和CPU有区别吗?

阿里云服务器vCPU是什么&#xff1f;vCPU和CPU有什么区别&#xff1f;CPU是指云服务器的中央处理器&#xff0c;一个CPU可以包含若干个物理核&#xff0c;一台云服务器ECS实例的CPU选项由CPU物理核心数和每核线程数决定&#xff0c;通过超线程HT&#xff08;Hyper-Threading&am…

探索三维世界【3】:Three.js 的 Geometry 几何体 与 Material 材质

探索三维世界【3】&#xff1a;Three.js 的 Material 材质 1、Geometry几何体2、Material 材质3、创建平面与材质4、多平面渲染 1、Geometry几何体 Three.js中的几何体Geometry是构成3D模型的基本单元之一&#xff0c;它定义了一个物体的形状和大小。Geometry包含了物体的顶点…

RK3568平台开发系列讲解(网络篇)网络包的接收过程

🚀返回专栏总目录 文章目录 一、内核接收网络包过程二、用户态读取网络包过程沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇我们一起来梳理下网络包的接收过程。 一、内核接收网络包过程 硬件网卡接收到网络包之后,通过 DMA 技术,将网络包放入 Ring Buffer;…

PyTorch数据加载工具:高效处理常见数据集的利器

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

HTTP 缓存新鲜度 max-age

新鲜度 理论上来讲&#xff0c;当一个资源被缓存存储后&#xff0c;该资源应该可以被永久存储在缓存中。由于缓存只有有限的空间用于存储资源副本&#xff0c;所以缓存会定期地将一些副本删除&#xff0c;这个过程叫做缓存驱逐。另一方面&#xff0c;当服务器上面的资源进行了更…

使用ControlNet控制Stable-Diffusion出图人物的姿势

概述 在Stable-Diffusion&#xff08;以下简称SD&#xff09;出图中&#xff0c;我们往往需要对出图人物的姿势进行控制&#xff0c;这里我使用一个比较简单上手的方法&#xff0c;通过ControlNet可以很方便地对画面风格&#xff0c;人物姿势进行控制&#xff0c;从而生成更加…

Python —— Windows10下训练Yolov5分割模型并测试

附:Python —— Windows10下配置Pytorch环境、进行训练模型并测试(完整流程,附有视频)   效果 手机拍摄一段工位视频,上传到win10训练了yolov5分割鼠标的样本后推理效果截图。 训练准备 1、查看自己下载的Yolov5源码是否存在"segment"文件夹,该文件夹下存在分…

【Python入门篇】——Python基础语法(字面量注释与变量)

作者简介&#xff1a; 辭七七&#xff0c;目前大一&#xff0c;正在学习C/C&#xff0c;Java&#xff0c;Python等 作者主页&#xff1a; 七七的个人主页 文章收录专栏&#xff1a; Python入门&#xff0c;本专栏主要内容为Python的基础语法&#xff0c;Python中的选择循环语句…

有限等待忙等、让权等待死等、互斥遵循的几大原则——参考《天勤操作系统》,柳婼的博客

参考柳婼的博客 一、 有限等待&&死等 有限等待: 对请求访问的临界资源的进程&#xff0c;应该保证有限的时间进入临界区&#xff0c;以免陷入死等状态。受惠的是进程自己 死等: 进程在有限时间内根本不能进入临界区&#xff0c;而一直尝试进入陷入一种无结果的等待状…

在字节跳动做了6年软件测试,4月无情被辞,想给划水的兄弟提个醒

先简单交代一下背景吧&#xff0c;某不知名 985 的本硕&#xff0c;17 年毕业加入字节&#xff0c;以“人员优化”的名义无情被裁员&#xff0c;之后跳槽到了有赞&#xff0c;一直从事软件测试的工作。之前没有实习经历&#xff0c;算是6年的工作经验吧。 这6年之间完成了一次…

TIM编码器接口

一、知识点 1、Encoder Interface 编码器接口的工作流程 编码器接口可接收增量&#xff08;正交&#xff09;编码器的信号&#xff0c;根据编码器旋转产生的正交信号脉冲&#xff0c;自动控制CNT自增或自减&#xff0c;从而指示编码器的位置、旋转方向和旋转速度 2、编码器接口…

6.2.1邻接矩阵法

接下来我们将认识图的几种存储结构&#xff1a; 邻接矩阵&#xff0c;邻接表&#xff0c;十字链表&#xff0c;邻接多重表 图的存储 1&#xff09;邻接矩阵法 0表示邻接vertex不邻接 只需要一个二位数组就可以实现&#xff1a; 顶点虽然是char类型&#xff0c;但可以存储更加…

搭建vue3+vite工程

搭建vue3vite工程 目录 搭建vue3vite工程 一、官方-文档-快速上手 二、详细截图及步骤 2.1、安装nvm 2.2、 用nvm安装多版本可切换的node.js版本 2.3、 按照官方文档初始化最近版本的vue3 三、脚本配置与调试 3.1、"2.3、"所产生的配置及脚本命令 3.2、脚本…

SpringCloud学习笔记06

九十五、Cloud Alibaba简介 0、why会出现SpringCloud alibaba Spring Cloud Netflix项目进入维护模式 1、是什么 官网&#xff1a;spring-cloud-alibaba/README-zh.md at 2.2.x alibaba/spring-cloud-alibaba GitHub 2、能干嘛 3、去哪下 spring-cloud-alibaba/README-…