learn.lianglianglee.com/专栏/软件工程之美/31 软件测试要为产品质量负责吗?.md.html
2022-05-11 18:57:05 +08:00

1155 lines
32 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>31 软件测试要为产品质量负责吗?.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/软件工程之美/00 开篇词 你为什么应该学好软件工程?.md.html">00 开篇词 你为什么应该学好软件工程?.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/01 到底应该怎样理解软件工程?.md.html">01 到底应该怎样理解软件工程?.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/02 工程思维:把每件事都当作一个项目来推进.md.html">02 工程思维:把每件事都当作一个项目来推进.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/03 瀑布模型:像工厂流水线一样把软件开发分层化.md.html">03 瀑布模型:像工厂流水线一样把软件开发分层化.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/04 瀑布模型之外,还有哪些开发模型?.md.html">04 瀑布模型之外,还有哪些开发模型?.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/05 敏捷开发到底是想解决什么问题?.md.html">05 敏捷开发到底是想解决什么问题?.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/06 大厂都在用哪些敏捷方法?(上).md.html">06 大厂都在用哪些敏捷方法?(上).md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/07 大厂都在用哪些敏捷方法?(下).md.html">07 大厂都在用哪些敏捷方法?(下).md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/08 怎样平衡软件质量与时间成本范围的关系?.md.html">08 怎样平衡软件质量与时间成本范围的关系?.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/09 为什么软件工程项目普遍不重视可行性分析?.md.html">09 为什么软件工程项目普遍不重视可行性分析?.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/10 如果你想技术转管理,先来试试管好一个项目.md.html">10 如果你想技术转管理,先来试试管好一个项目.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/11 项目计划:代码未动,计划先行.md.html">11 项目计划:代码未动,计划先行.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/12 流程和规范:红绿灯不是约束,而是用来提高效率.md.html">12 流程和规范:红绿灯不是约束,而是用来提高效率.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/13 白天开会,加班写代码的节奏怎么破?.md.html">13 白天开会,加班写代码的节奏怎么破?.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/14 项目管理工具:一切管理问题,都应思考能否通过工具解决.md.html">14 项目管理工具:一切管理问题,都应思考能否通过工具解决.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/15 风险管理不能盲目乐观凡事都应该有B计划.md.html">15 风险管理不能盲目乐观凡事都应该有B计划.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/16 怎样才能写好项目文档?.md.html">16 怎样才能写好项目文档?.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/17 需求分析到底要分析什么?怎么分析?.md.html">17 需求分析到底要分析什么?怎么分析?.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/18 原型设计:如何用最小的代价完成产品特性?.md.html">18 原型设计:如何用最小的代价完成产品特性?.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/19 作为程序员,你应该有产品意识.md.html">19 作为程序员,你应该有产品意识.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/20 如何应对让人头疼的需求变更问题?.md.html">20 如何应对让人头疼的需求变更问题?.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/21 架构设计:普通程序员也能实现复杂系统?.md.html">21 架构设计:普通程序员也能实现复杂系统?.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/22 如何为项目做好技术选型?.md.html">22 如何为项目做好技术选型?.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/23 架构师:不想当架构师的程序员不是好程序员.md.html">23 架构师:不想当架构师的程序员不是好程序员.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/24 技术债务:是继续修修补补凑合着用,还是推翻重来?.md.html">24 技术债务:是继续修修补补凑合着用,还是推翻重来?.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/25 有哪些方法可以提高开发效率?.md.html">25 有哪些方法可以提高开发效率?.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/26 持续交付:如何做到随时发布新版本到生产环境?.md.html">26 持续交付:如何做到随时发布新版本到生产环境?.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/27 软件工程师的核心竞争力是什么?(上).md.html">27 软件工程师的核心竞争力是什么?(上).md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/28 软件工程师的核心竞争力是什么?(下).md.html">28 软件工程师的核心竞争力是什么?(下).md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/29 自动化测试如何把Bug杀死在摇篮里.md.html">29 自动化测试如何把Bug杀死在摇篮里.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/30 用好源代码管理工具,让你的协作更高效.md.html">30 用好源代码管理工具,让你的协作更高效.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/软件工程之美/31 软件测试要为产品质量负责吗?.md.html">31 软件测试要为产品质量负责吗?.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/32 软件测试:什么样的公司需要专职测试?.md.html">32 软件测试:什么样的公司需要专职测试?.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/33 测试工具为什么不应该通过QQ微信邮件报Bug.md.html">33 测试工具为什么不应该通过QQ微信邮件报Bug.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/34 账号密码泄露成灾,应该怎样预防?.md.html">34 账号密码泄露成灾,应该怎样预防?.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/35 版本发布:软件上线只是新的开始.md.html">35 版本发布:软件上线只是新的开始.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/36 DevOps工程师到底要做什么事情.md.html">36 DevOps工程师到底要做什么事情.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/37 遇到线上故障,你和高手的差距在哪里?.md.html">37 遇到线上故障,你和高手的差距在哪里?.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/38 日志管理:如何借助工具快速发现和定位产品问题 .md.html">38 日志管理:如何借助工具快速发现和定位产品问题 .md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/39 项目总结:做好项目复盘,把经验变成能力.md.html">39 项目总结:做好项目复盘,把经验变成能力.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/40 最佳实践:小团队如何应用软件工程?.md.html">40 最佳实践:小团队如何应用软件工程?.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/41 为什么程序员的业余项目大多都死了?.md.html">41 为什么程序员的业余项目大多都死了?.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/42 反面案例:盘点那些失败的软件项目.md.html">42 反面案例:盘点那些失败的软件项目.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/43 以VS Code为例看大型开源项目是如何应用软件工程的.md.html">43 以VS Code为例看大型开源项目是如何应用软件工程的.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/44 微软、谷歌、阿里巴巴等大厂是怎样应用软件工程的?.md.html">44 微软、谷歌、阿里巴巴等大厂是怎样应用软件工程的?.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/45 从软件工程的角度看微服务、云计算、人工智能这些新技术.md.html">45 从软件工程的角度看微服务、云计算、人工智能这些新技术.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/一问一答第1期 30个软件开发常见问题解决策略.md.html">一问一答第1期 30个软件开发常见问题解决策略.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/一问一答第2期 30个软件开发常见问题解决策略.md.html">一问一答第2期 30个软件开发常见问题解决策略.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/一问一答第3期 18个软件开发常见问题解决策略.md.html">一问一答第3期 18个软件开发常见问题解决策略.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/一问一答第4期 14个软件开发常见问题解决策略.md.html">一问一答第4期 14个软件开发常见问题解决策略.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/一问一答第5期 22个软件开发常见问题解决策略.md.html">一问一答第5期 22个软件开发常见问题解决策略.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/学习攻略 怎样学好软件工程?.md.html">学习攻略 怎样学好软件工程?.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/特别放送 从软件工程的角度解读任正非的新年公开信.md.html">特别放送 从软件工程的角度解读任正非的新年公开信.md.html</a>
</li>
<li>
<a href="/专栏/软件工程之美/结束语 万事皆项目,软件工程无处不在.md.html">结束语 万事皆项目,软件工程无处不在.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>31 软件测试要为产品质量负责吗?</h1>
<p>你好,我是宝玉。从这一篇开始,我们将进入软件工程中的测试模块的学习。</p>
<p>说到软件测试,你一定不会陌生,尤其是如果你做开发相关岗位的话,一定是对测试又爱又恨,一方面测试从你的程序找出 Bug然后你还要费心去修复另一方面测试帮你发现 Bug修复后能很好的提升质量。</p>
<p>正因为测试能发现软件中的质量问题,通过测试能有效提升软件质量,慢慢的大家就觉得软件测试能保障质量,所以测试要对质量负责。开发也会对测试产生依赖心理,很多功能模块实现后,就扔给测试人员去测试。</p>
<p>上线后,如果因为有测试漏测导致的 Bug测试人员还要为质量问题背锅受到责备。上面这样的场景到现在也还在很多软件项目中上演。但这对测试人员其实是不公平的。</p>
<p>因为软件开发是多个环节组成的,从最开始的需求,到后面的设计、开发,每个环节都可能会导致质量问题,<strong>而测试只能对已经开发完成的软件产品进行检测,并不能干预整个过程。</strong></p>
<p>比如说测试是无法对开发写的代码直接测试的,只能基于软件功能去测试,也就是说对于代码的质量,测试人员其实是没有什么办法的。</p>
<p>那到底谁应该为产品质量负责呢?在回答这个问题之前,你不妨先思考一个更本质的问题:什么是软件产品质量?</p>
<h2>什么是软件产品质量?</h2>
<p>我以前以为,软件质量就是由 Bug 数量、性能高低、安全性等指标决定的,现在看来这样划分其实并不全面。</p>
<p>因为不同的人对软件质量好坏的评判角度是不同的。比如对用户来说,更看重产品是不是满足需求,是不是美观好用;对开发来说,看重的是代码质量是不是高,是不是好维护;对于软件测试人员而言,看重的是 Bug 数量、安全、性能等指标;对于项目负责人,看重的是整个开发过程的质量,是不是成本可控、如期完成。</p>
<p>在这个问题上我比较认同《The Three Aspects of Software Quality: Functional, Structural, and Process》这篇文章作者 David Chappell 的观点,他把软件质量分成了三个考量方面:功能、结构和流程。对于他提的“结构质量”,我认为定义为“代码质量”更贴切,也就是说,<strong>功能质量、代码质量和过程质量这三个方面组合在一起,很好地概括了软件质量。</strong></p>
<p>所有的软件开发都是从一个想法开始的,用户需要一个软件,有人出钱,然后开发团队实施,把想法变成需求,需求变成设计,设计变成代码,代码变成软件。</p>
<ul>
<li><strong>功能质量</strong></li>
</ul>
<p>最终用户得到是软件,体验的是软件的功能,功能的质量直接决定了产品的质量。</p>
<p>满足用户需求是对功能质量最基础的要求。在这个基础上Bug 数量、性能、UI/UX 都是很重要的质量指标。如果你的软件 Bug 太多、性能差,用户不会满意;界面难看,操作体验也很差,这些因素都决定了你产品的功能质量。</p>
<ul>
<li><strong>代码质量</strong></li>
</ul>
<p>构成软件最重要的部分是代码,代码质量指的是实现软件功能的架构和代码的质量。代码的质量主要体现在以下这些方面:</p>
<ol>
<li>
<p>代码的可维护性,也就是在不影响稳定性的前提下,是否能方便地添加或者修改现有的代码;</p>
</li>
<li>
<p>代码的可读性,代码是否容易理解,是否能快速上手;</p>
</li>
<li>
<p>代码的执行效率,代码执行效率直接影响了软件性能;</p>
</li>
<li>
<p>代码的安全性,是否有安全漏洞,安全性是代码质量很重要的一个指标;</p>
</li>
<li>
<p>代码的可测试性,代码是否能使用单元测试、集成测试进行测试验证。</p>
</li>
</ol>
<p>虽然用户不能直接感知到代码,但是代码质量高低会直接影响功能质量,同时代码质量低也会影响后续的维护升级。</p>
<ul>
<li><strong>过程质量</strong></li>
</ul>
<p>软件的开发离不开软件工程,离不开项目管理。软件开发过程的质量决定了你的项目是否能如期完成,开发成本是否在预算之内。</p>
<p>过程质量虽然也是用户不能直接感知的,但是过程质量会直接影响代码质量和功能质量,甚至是产品的成败。</p>
<p>以上就是软件质量的三个方面,<strong>软件质量从来不是单方面质量决定的,通常是几方面质量因素相互影响,共同决定的。</strong></p>
<p>比如说改进流程,增加了自动化测试的覆盖,应用了持续集成,这样可以提高代码质量和功能质量。或者说对代码质量过于追求,又可能会影响过程质量,例如时间延期,成本超标。</p>
<h2>谁该为产品质量负责?</h2>
<p>在梳理清楚产品质量的问题后,我们就可以来讨论谁该为产品质量负责的话题了。</p>
<p><strong>既然产品质量是由功能质量、代码质量和过程质量共同决定的,那么对产品质量负责,意味着要对这三方面共同负责。</strong></p>
<p>在说到责任之前,我想补充一下权责对等的问题。责任和权力是需要对等的,比如说你让开发人员对软件开发过程负责,那么前提是他必须有权力去影响和控制开发过程,否则离开权力谈责任就是耍流氓了。</p>
<p>然后,我们再一起看看项目中的主要角色,谁最应该为产品质量负责?</p>
<p>软件测试,可以对功能质量负责,对软件产品进行测试验收,以确保产品满足功能需求,有好的功能质量。但是通常不能对代码质量和过程质量负责。</p>
<p>开发人员,可以对代码质量负责,也可以写测试代码,通过自动化的方式做功能测试,虽然还不能完全替代手工测试的作用,所以也可以算得上对功能质量负责。但开发人员通常对过程质量影响有限。</p>
<p>项目负责人,可以对过程质量负责,而且过程质量的水平高低,会间接影响代码质量和功能质量。但因为项目负责人不直接编码和测试,所以无法直接影响代码质量和功能质量。</p>
<p>所以综上,我觉得如果要排序的话,软件质量的首要负责人是项目负责人,其次是开发人员,然后才是软件测试。</p>
<p>虽然从权责的角度看,项目负责人是最应该对项目质量负责的,但是从效果来说,却是开发人员对项目质量负责最有利。</p>
<p>首先,开发人员是唯一能直接影响代码质量、能对代码质量负责的人。开发人员能更容易地找到代码中的 Bug更容易通过架构设计、自动化测试代码等手段保证好代码质量提升测试效率。</p>
<p>现在软件开发的发展趋势也是如此,软件测试的很大一部分手工测试工作已经被自动化代替。</p>
<p>所以很多公司就让开发负责产品质量,甚至都不设测试岗位,典型代表就是 Facebook。开发人员自己写代码实现功能然后写自动化测试代码对功能进行测试最后上线。这样不仅自己测试能保证功能的质量又能通过自己写单元测试、集成测试来保证代码的质量。</p>
<p>当然,开发人员对功能质量负责,意味着必须在实现功能的同时,还要考虑如何去测试这个功能,这样让代码更具有可测试性,这就对开发人员的要求更高了。</p>
<p>就像 Facebook 强调的“Be there from start to ship”就是让每个工程师能自始至终地负责产品。从想法到原型设计、到产品开发、上线和维护全部是工程师自己完成。</p>
<p>我们不需要做到 Facebook 那样,从头到尾都一个人搞定,但至少,作为开发人员,我们可以对代码质量有更高要求,让项目有更多自动化代码的覆盖;可以在交付测试之前自己先测试一遍。</p>
<p>这样的话,开发就可以真正做到对代码质量和功能质量负责。如果你还想对过程质量也能负责,那么敏捷开发中一些理念是有可取之处的。</p>
<p>敏捷开发中强调的是:项目的所有人一起为产品质量负责,人人为产品质量负责。</p>
<p>但人人为质量负责,很容易变成一句口号而很难落实。就像三个和尚没水喝的故事里面那样,当质量变成每个人的责任时,就没有人真正为质量负责了。所以我们不只是要学习敏捷开发中的理念,还要学习它一些具体的方法。</p>
<h2>如何做到“人人为产品质量负责”?</h2>
<p>只有真正在团队中建立了一种重视产品质量的文化,每个人才会确确实实地对质量负责。那么有哪些方法可以帮助团队建立这种“人人都重视产品质量”的文化呢?</p>
<p>首先,可以参考敏捷开发中的扁平化管理。在敏捷开发中没有项目经理,只有产品负责人,而产品负责人更多是充当一种服务型的角色。大家都很平等,也就是说每个人都有权力去影响到项目过程,实现权责对等,大家才会为过程质量负责。</p>
<p>其次,可以选择将团队拆小。敏捷开发中的团队规模都不大,大的开发团队拆分成了小的开发小组,每个组人数都不多。人数多的时候容易推诿扯皮,但如果人少,每个人就必须要承担更多的责任,这有助于形成人人重视产品质量的文化。</p>
<p>另外,也可以鼓励工种之间的融合,例如开发人员多写自动化测试代码;测试人员在开发人员写自动化测试时,提供帮助,例如设计测试用例。这样不只是局限于各自负责的质量领域,也同时关注其他质量领域。</p>
<p>最后就是制定相应的制度,鼓励大家重视质量。比如说:</p>
<ul>
<li>
<p>每个 Sprint 都有项目回顾会议,每个人都可以针对质量提出有效的建议,最终将这些建议落到实处;</p>
</li>
<li>
<p>出现质量问题,不是推卸责任,而是分析原因,及时修复,避免以后出现类似问题。</p>
</li>
</ul>
<p>要做到“人人为产品质量负责”,还是要像上面提到的一样,要落到行动而不是口号上,组织上扁平化、小型化,分工上打破岗位墙,制度上鼓励大家重视质量,才能真正建立重视产品质量的文化,一起把产品的质量提升上去。</p>
<h2>总结</h2>
<p>今天我带你一起探讨了一个在软件项目中的常见问题:软件测试要为产品质量负责吗?</p>
<p>保证软件高质量,并非只是测试人员的责任。软件质量体现在功能质量、代码质量和过程质量这三个方面,对产品质量负责,也意味着要对这三方面共同负责。</p>
<p>软件测试,不能影响代码质量和过程质量,所以并不需要为产品质量负责,项目负责人能直接影响过程质量,也能间接影响代码质量和功能质量,应该为产品质量负责。对于开发人员而言,不应只是局限于对代码质量负责,还应该注意功能质量。</p>
<p>对产品质量,最理想的状态还是能做到人人都为产品质量负责,而达到这样的目标,还是需要建立一种重视质量的文化,每个人才会确确实实地对质量负责。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/软件工程之美/30 用好源代码管理工具,让你的协作更高效.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/软件工程之美/32 软件测试:什么样的公司需要专职测试?.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70997c807e923cfa","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>