高品质编程

高品质编程

公众号原帖:https://mp.weixin.qq.com/s/hCjYQoeryzTl42Bl6p1OlQ

1 前言
1.1 公司研发现状
加入奇安信已经一年多了,在这一年多的时间里看到公司的程序员拥有勤劳善良,勇于接受挑战,敢于承担责任,不计较个人得失的各种美德,在面对繁重的开发任务,各种资源不足的情况时,大家一般都是想着怎么把事情做好,很少听到有人抱怨,正是在所有人的努力之下,我们公司才能持续不断的高速发展。
但是从另一方面来看,虽然我们每天很努力,但其实产出效率并不高,产品质量也不高,我们的产品存在各种各样影响用户体验的问题,同时因为存在这些问题我们必须不断的去填各种坑,从而每天疲于奔命,甚至连思考的时间都没有。
固然有各种各样外部的因素导致了以上的问题,例如项目周期短,突发情况多等,这些属于人力不可抗拒因素,很难完全避免。但是从另一方面来看,我们自身也还有巨大的提升空间,试想一下,如果我们写出来的代码天衣无缝,提测之后毫无破绽,是不是能节约大量的测试时间?交付给客户后是不是产品问题也会少很多?那么我们是不是就可以减少大量处理突发情况的开发时间和测试及发布的时间?虽然真正实现这样的目标有一定难度,但也并非完全不可能,至少我们可以通过动脑和动手,尽量提高产品质量,减少反复折腾的时间。
本次分享无法解决那些人力不可抗拒的因素,但通过本次分享我们可以尝试解决一些我们自身的原因,希望这次分享能帮助大家提高自身的专业素养,提升代码的可靠性,从而提升产品质量,最终提升研发效率,降低研发成本。
高速发展的公司有一个普遍现象,早期由于各种原因代码质量一般不高,而新加入的同学在进入公司后看到的第一份代码就是这样的代码,会很自然的认为这么写是合理的,久而久之代码就会变得越来越难维护。虽然从专业的角度上看很难理解为什么代码会写成这样,但对于当事人来说,可能未必会觉得自己这么写有什么不对,很可能他们自己认为这样写已经是最佳方案了。
1.2 高品质编程的定义
对于高品质编程,很难有一个大家公认的标准,大家可能会认为,凭啥你的标准就是高品质的,而我的标准品质就不高?
这里对高品质编程做一个定义:所谓高品质编程,不但结果高品质,而且过程也必须是高品质,具体来说指的是开发过程效率高、程序运行稳定可靠、性能高,代码简洁美观、易于维护。总而言之,可以把一切关于编程的美好的词汇都放在高品质编程上。
这么说可能大家还是会觉得太抽象,缺少一个能直观感受到的标准,我们不妨换个角度来看,如果公司现在做的是自动驾驶方面的产品,这个自动驾驶的代码是你写的,那么请问你敢不敢每天坐在自己开发的自动驾驶的车里上下班?如果敢的话,那说明代码质量还是过硬的,否则说明质量有问题。
高品质编程的目标是整个团队、甚至整个公司的高品质编程。如果只是某个人的模块高品质,虽然也很好,但还不够。我们是一个团队,整个团队是协同作战,大家需要有相同的做事方式并形成默契。如果是在战场上,团队成员就是可以互相把后背托付给对方的兄弟,在项目中大家是可以放心互相调用接口的同事。如果我们在做自动驾驶方面的产品,那我们的目标就是能放心的坐在队友开发出来的自动驾驶车辆里上下班。
关于高质量编程,其实是一个很大的课题,本次分享时间有限,无法讲太多内容,只能先讲一些大道理,例如整体的介绍高品质编程的目标,以及我们应该有什么样的习惯才能让自己写出来的代码有较高的品质,最后再介绍一下什么是好的接口。未来还计划再做一系列分享,基于C++语言具体从编码角度上介绍什么是高品质编程,什么样的代码才能称得上是高品质的等等。

2 几个错误的观点
在文章的开始,必须先纠正几个流传已久的错误观点,其中有一些观点是被人断章取义,还有一些观点是本身就是错误的。
2.1 代码质量不高是因为项目进度紧
大家肯定在各种不同场合听到过这句话,甚至可能大家自己都说过类似的话,但其实这句话是不对的,项目进度紧并不是降低代码质量的理由,越是项目进度紧越是要写出高质量的代码,这样才能节约测试的时间,最终把缺少的时间补回来。
无论遇到什么紧急情况,做事一定不要乱了方寸,作为老司机我比较喜欢用开车作例子,如果我们经常开车(或者坐出租车)就会发现,在上下班高峰期,即使你是舒马赫,车开的飞快,其实到公司的时间并不会比正常开车快多少,20公里的路程,猛踩油门最多也只能提早一两分钟到公司。在上下班高峰期开车,决定是否会迟到的主要因素并不在于油门踩的有多狠,而在于每个路口等待绿灯的时间,以及路上每个容易产生拥堵的节点所花的时间。如果因为要赶时间,不注意安全(降低开车质量),总是猛踩油门,万一碰上点啥事,那就什么都白费了,前面不管节约了多少时间,最终一定会迟到。真正的老司机开车,该快的地方快,该慢的地方慢,一路上总是游刃有余,无论是踩油门还是刹车都是恰到好处,在任何时候都会提前留有余量,提前预判各种可能出现的隐患,这样才能又快又稳的在指定时间内到达目的地。
客观上项目进度紧,确实会造成一些问题,例如设计时间不足,测试时间不足等,这种时候非常考验研发人员的功力,越是这种时候越需要多思考,设计出最简洁的实现方案,编码的时候提前预判各种可能出现的情况,在提交测试之前就已经把各种可能出现的漏洞全部堵上,提测后的东西没有任何明显的Bug,因为每修复一次Bug,都涉及到重新编写代码、自测、编译、提测等过程,这些就相当于道路上的堵车点,减少几个堵车点比猛踩几脚油门效果要好得多。
2.2 不要重复造轮子
应该说这句话还是有一定道理的,确实我们没有必要重复造轮子,有现成东西可用的时候直接拿来用就行,既方便又好用。在现在这个时代,各种优质的第三方库唾手可得,而且往往比自己写的库质量更高,完全没有理由不使用这些优质的库而自己去重复造轮子。
但从另一方面看,这句话又是存在严重的误导性的,很多程序员会把不要重复造轮子,理解成不要去学造轮子,甚至还有人理解成不要去了解轮子。如果这么理解的话就有问题了,除非我们对自己的定位就是一个初级码农,一辈子只想干一些搬砖的粗活,到了一定岁数之后能接受被市场淘汰的命运。
但凡我们对自己要求高一点的话,就应该花时间去了解一下自己用到的每个库,力求对里面的实现原理了如指掌,必要的时候还应该自己动手去实现一遍,站在作者的立场上去理解这个库为什么要这么设计,这么设计的好处是什么,不足之处是什么,应该在什么场景下使用才是合理的等等。一个职业车手,一定对车里的各种部件极其熟悉,跟汽车有关的任何方面都是专家,对汽车内部的发动机、变速箱、离合器等的原理了然于胸,只有这样才能发挥出这辆车的最大性能,驾驶的时候才能如臂使指,指哪打哪。同样军人在进行射击训练之前,一般也会先练习拆卸和组装枪支,军队里的神枪手,完全可以做到闭着眼睛都能把枪拆散再组装回去。
举这么多例子的目的在于,我们必须对自己使用的第三方库非常了解才能用好它,才能在适当的使用场景下选择合适的库,才能发挥出这个库最大的优点,同时避免不足。而了解一个第三方库最有效的方式就是自己亲自动手去实现一遍,不要求所有功能全部都实现一遍,但应该把里面主要功能自己亲手实现一遍(可以不用过多考虑细节,但要理解原理)。
在开发过程中,我们应该有制造轮子的能力,有能力但不用,跟没有能力,这是完全不同的两种水平,我们有制造轮子的能力,在万一没有轮子可用的时候,可以随时自己弄一个出来,如果连这能力都没有,真碰上这种情况,就会措手不及。
3 两条指导原则
3.1 客户第一
客户第一这说法,一般在产品和销售行业中听的比较多,在研发岗位上很少听到有人这么说,但客户第一这一条,是优秀程序员最应该拥有的好习惯。
客户第一本质上是一种利他行为。很多人会想,我工作的时候过多的考虑利他,那自己不就吃亏了吗?这个要看我们对自己是怎么定位的,如果只是把自己定位成一个初级码农,做一天和尚撞一天钟,不考虑今后的成长,那确实只需要做好自己的事情就行。但如果对自己有更高要求的话,会发现利他行为最终一定是利己的,这是一种双赢的行为。
从研发角度上看,以下人员都是自己的客户:
可能会调用你模块的人
可能会阅读你代码的人
可能会拷贝你代码的人
可能会修改你代码的人
测试人员
……
我们写出来的代码,终究有一天会以各种形式到这些人员手上,我们如果能把他们当成客户看待,那我们写代码的时候就应该注意代码的可读性、可维护性和可扩展性,要让未来得到这些代码的人看到代码的时候由衷的赞叹这代码写的真是不错,而不是未来有一天代码被别人看到的时候,被人在背后骂:这写的啥玩意儿?
如果能一贯坚持客户第一,写出来的代码自然无可挑剔,慢慢就会形成一个非常好的口碑,别人看到你写的代码会很放心,QA同事收到你提交测试的模块也会心里有底,知道肯定不会什么严重的问题,你的队友未来也敢坐在你开发出来的自动驾驶汽车里上下班。
如果团队中所有成员都能坚持客户第一,这就是一个人人为我,我为人人的和谐团队,这个团队的战斗力一定是无与伦比的,互相之间配合会非常有默契,接到任务后模块一划分,接口简单设计一下,大家心里就都会有数,互相都能信任对方提供的接口,然后各自开发就行。在遇到困难的时候大家都能主动互相补位,会减少很多沟通成本,避免出现由于沟通导致的浪费,能有更多的时间用在项目上,最终产出的产品质量也必然会很高。
3.2 勿以恶小而为之,勿以善小而不为
如果我们经常关注一些新闻就能发现,很多严重事故往往起源于某个时候一些大家不回事的小问题,这些事情如果早期有人能顺手做一下几乎没有什么成本,但却可以避免后面出现的重大事故。例如酒驾之类的,大多数人都知道酒驾不对,但总有人觉得这小事,不当回事,最后就酿出大事故。这个道理所有人都明白,但总有人会因为各种原因犯这种低级错误,可能因为一时犯懒不想这么做,也可能是因为心存侥幸,觉得不这么做无所谓,更多的时候是根本没有意识到某件事情是不对的。
举个研发里的例子,我们分配内存的时候,人人都知道分配了内存之后应该检查一下指针是否为空,但总有些人明知应该去检查内存却不去检查。大多数情况下内存分配确实极少失败,而且如果真失败很大可能性是内存确实分配不出来了,其实程序继续执行也没有意义了,但有时候我们也会遇上总内存足够,但找不到一块连续的大小足够的内存的情况,这种时候内存分配也是会失败的,如果也不检查,程序就崩溃了,如果检查一下,我们还是有机会做点比的事情的。如果这个程序是自动驾驶系统,因为懒得检查内存是否分配成功这一个小小的恶念,就会出严重的交通事故,可能某个乘客就这么死在你手上了。
在编写代码时,当脑子里冒出一个要偷懒的念头时,一定要引起警惕,一定要尽快把这个念头摒弃掉,现在偷懒一时爽,其实也就少写了几行代码,未来很可能会花几百倍的时间和精力去填这一时偷懒挖下的坑,有时候一个T1级别程序员挖出来的坑,公司甚至要出动T10级别的专家去现场解决,实在是得不偿失,只要稍微算一下帐我们就能做出正确的选择。
4 优秀程序员的做事方式
4.1 养成思考的习惯
我们欣赏埋头苦干的老黄牛精神,这是一种优良品质和传统美德,值得所有人学习。但我们在埋头苦干的同时,更要抬头看路,并且养成思考的习惯,做事三四而后行,找准方向再动手,动手之后每一步都在自己计算和掌控中,这样才能做到从容不迫,胸有成竹,举手投足无不恰到好处。接到一个需求后先缜密思考一下,挖掘一下这个需求背后客户真正想要的本质东西是什么,然后直奔主题去做事,这样才是效率最高的做法。反之如果不思考,上来就做事,可能未必是效率最高的方式。这里举一个例子,多年前360安全卫士在覆盖安装的时候会重新创建开始菜单项和桌面快捷方式,某一天有用户反馈,说我都已经把桌面快捷方式删掉了,你360覆盖安装的时候又给我创建出来了,用户在论坛上大骂360是流氓。产品经理收到用户反馈后迅速给出一个解决方案:在覆盖安装的时先检查桌面快捷方式在不在,如果桌面快捷方式不在了,那就不要再去创建快捷方式了,同样开始菜单也做一样的处理。这个解决方案看上去合情合理,毫无破绽,照做的话肯定能解决问题,开发成本也不高,方案是可行的。但如果我们养成思考习惯的话,就能发现这个需求本质上几乎什么都不用做,安装程序在覆盖安装的时候根本就没必要去检测桌面和开始菜单上的图标是否存在,只需要在覆盖安装的时候不要去创建任何快捷方式就可以完美的解决这个问题,因为如果桌面快捷方式没有被删除,覆盖安装之后图标当然还在;反之如果桌面快捷方式已经被删除,覆盖安装之后当然也不会重新创建出来。前面产品经理给的方案,虽然成本确实也不高,但毕竟还得开发、编译、打包、测试和发布,测试用例还得区分各种不同场景,其实测试起来还是挺复杂的,而经过思考后的方案,只需要加个判断就行,简单测试一下就可以发布,几乎毫无成本。
思考也是学习新知识的重要途径,有些知识是可以通过换位思考自己找到答案的,我在使用某个第三方库,或者使用操作系统某些特性时,会假设这东西是我设计的,那么我就会去想如果这东西是我来做,我会怎么去实现它。经过思考后会得到一些方案,然后再去查资料或看代码印证一下自己想的方案跟作者的方案是不是一样,如果是一样,那我就对这东西非常了解了,如果不一样的,就比较一下不一样在哪,那种方案更好,比较完之后,我也对这东西非常了解了。
软件开发终究是一个脑力劳动,光靠埋头苦干和死记硬背是不够的,只有多思考才能把事情做好,自身能力也能迅速提高。
4.2 站在客户角度上编写程序
作为ToB的公司,我们做的产品必然会面临各种需要定制的场景,客户要求产品上出现一些自己单位的内容合情合理,而且这类需求一般工期还都比较短,需要快速交付。一般程序员的做法是为这个客户拉一个分支来解决这种定制问题,这么做确实能迅速把事情做完并交付。但这么做的后果是,未来我们很可能得维护两个分支,万一有Bug,我们需要改两份代码,并且发布两遍,如果万一这类定制需求多的话,情况还会更严重,到时候大量的时间会消耗在这种重复劳动上,每天疲于奔命,事情做的还很没有成就感。
面对这种情况,合理的做法是我们一开始就站在客户角度上编写程序,在开发过程中预判哪些内容未来客户可能会需要定制,提前预留配置的接口,一旦用户真的要定制时就不需要改代码,只需要改一下配置文件就可以解决,这显然能节约大量的成本。
对于有些目前没有做成配置,但客户要求定制的地方,我们在动手之前更应该仔细思考,设计合理的方案,宁可多写一些代码,也要避免拉分支。大部分定制需求技术上都没什么难度,其实是可以通过配置来解决的,多写一些代码支持配置属于一次性的工作,代价再大也比未来同时维护多个分支成本要低得多。客户的定制需求虽然层出不穷,但如果进行一些抽象和分类的话,需要定制的种类并不会太多,其实是可以有针对性的一一解决的。
4.3 与QA斗,其乐无穷
一个优秀的开发人员能站在QA的角度上去看问题,在开发过程中能提前预料到QA同学会怎么去测这个产品,既然都已经知道QA同学会怎么测了,那我们何不先把可能出现的Bug提前解决了,让QA同学测不出Bug呢?这么做明显可以节约大量的测试时间,同时开发人员自己也节约了大量的时间,完全可以利用这些节约出来的时间做点更有意义的事情。在一些工期特别短的紧急项目上,如果能做到让QA测不出Bug,那必然能快速保质保量的把产品发出去。这就是为什么越是项目进度紧越要讲代码质量的原因。
编码阶段站在QA的角度上看问题,能跟QA同学形成默契,能QA同学中建立信任,QA同学一看到你提测的东西心里就有底,在一些紧急项目里,这种默契和信任有助于让产品快速可靠的发布出去,我们应该珍惜这种信任。
4.4 优先解决重要但不紧急的问题
这个道理相信大家也都知道,但从现状上看,大家还是经常在做各种救火的事,去处理各种突发情况,原因是早期这方面没做好,把一些重要但不紧急的事情给拖成紧急的事情了。
从性价比上看,如果真的出现重要并且紧急的事情,那确实不得不先把这些事情解决了,但如果暂时没有出现紧急情况的时候,我们应该先把那些目前暂时不紧急,但是非常重要的事情先解决了,以免未来某一天突然暴雷,变成紧急事件。
仍然以开车作为例子,任何一个司机在开车时看见前方有情况发生,肯定不可能眼看要撞上了才去踩刹车或者变换方向,一定是只要看到前面有情况,提前几百米就做好准备,要么踩刹车要么变道,这样是正确的做法。
同样我们在日常工作中,对于一些未来可能会出现风险的地方,应该提前把这些风险点排除掉,不要等到最后这些事情真的发生了再动手,这种时候往往就晚了,就算能解决成本也很高。
我们中学的时候学过一篇课文:《扁鹊见蔡桓公》就很能说明问题,很多时候就是有些小问题大家不当回事,等到拖成大问题后再去解决就已经晚了,我们可以想一想平时自己有没有犯类似的错误,有的话赶紧提前处理。
4.5 不要写重复的代码
我们经常会在项目中看到一些相似度很高,甚至完全一样的代码,很多这样的代码都是拷贝粘贴而来,而且很多时候去做拷贝粘贴的人还会把别人的Bug也一起拷贝过来。
重复代码首先从感官上就给人一种不美观的感觉,代码会显得很臃肿,万一其中一个地方有Bug,一般来说所有重复使用的地方都有同样问题,要改的话所有地方都得改,非常麻烦。正确的做法是把这些重复的代码封装成函数或接口,在不同地方调用即可,而不是把代码到处拷贝。如果重复的代码里有个别语句不一样,这种情况下只需要在封装后的函数里增加几个参数就可以解决,总而言之要避免项目中出现重复的代码。
我们并不是说完全不允许拷贝代码,但正如前面所说,软件开发的工作归根到底是脑力劳动,不要把它干成体力活。我们如果要拷贝代码,也应该在拷贝的同时积极动脑,判断里面哪些内容是必不可少的,多余的东西不要拷贝过来,拷贝过来的代码也应该经过改造再用,而不是直接就粘贴过来。拷贝过来的代码本质上也属于使用第三方库,同样需要理解透彻之后才能用。
我们也要尽量避免做重复性的工作,作为程序员,在面对重复工作的时候,应该想办法编写工具(脚本也可以认为是工具)来自动完成,这些重复性工作用工具完成效率更高,使用起来更方便,本来有些只有你能做的事情,以后谁再找上门来的时候直接把工具给他,他自己就能完成了,这是一次投入,终身受益的事。
4.6 不要挑战概率
在编程这事上,除非自己主动使用了随机数,否则无论如何都不应该基于概率的思想去解决问题,不能认为某个东西出问题的几率很低这种想法,在编程这事上,应该每一步都在掌控中,程序应该严格按照预想的路径执行,这才是合理的程序设计。
有时候能看到一些老代码里存在一些Sleep几秒的代码,往往都是某个时刻为了解决某个Bug而加上去的,我们先不评论这种做法会不会有性能问题,仅就可靠性而言,这种Sleep的方式也是不可取的,因为根本不能保证这个Sleep的时间是不是在任何情况下都够用,万一Sleep的时间还是太短呢?难道我们再增大时间?类似这种Sleep的做法,只是降低了出错的概率,并不能彻底解决问题。
编写任何程序时不能心存侥幸,试图用减小概率的方式来解决问题,可能大多数情况下确实不会有问题,但在某些情况下是一定会出问题的,还是那个问题,如果这是一个自动驾驶的产品,你自己敢坐在自己写的代码的车里吗,你敢在自己的车里用Sleep去解决问题吗?
5 什么是好的接口
5.1 什么是接口
大家从各种教科书上能知道接口是做什么的,这里从朴素的角度,用相对接地气的方式来介绍一下接口在项目中的作用。
在个人单打独斗的时期,所有代码都是自己一个人写的,怎么写都没问题,所以早期程序员写程序经常没有严格意义上的接口,反正都是自己一个人搞定(其实好程序员即使一个人写程序,内部也还是会有接口概念的)。到了后来程序的规模开始变大,需要多个人一起完成某个产品的时候,为了大家能互不干扰的开发,就先互相约定好调用的接口,至于接口后面的实现,理论上大家都可以不用关心(其实最好还是应该关心一下实现的细节),只要接口能正常调用,大家就可以各干各的了,本来一个人需要做很久才能完成的事情,就可以由多个人一起用更短的时间完成了。如果接口设计合理的话,以后再开发别的程序,也可以直接使用这个接口,这样就实现了代码复用,提高了开发效率,降低了开发成本。
5.2 好接口的标准 - 信达雅
对于一个接口是否是好接口,不同的人会有不同的理解,在我看来一个好接口,必须满足以下标准:信、达、雅。
信、达、雅这个说法是我国清朝末年的思想家严复先生(中学历史课本上学到过他,《天演论》的翻译者)提出来的,是我国翻译界的通用标准,这里我借用这个概念给出好接口的标准。
信达雅的原意里,“信”指意义不悖原文,即是译文要准确,不偏离,不遗漏,也不要随意增减意思;“达”指不拘泥于原文形式,译文通顺明白;“雅”则指译文时选用的词语要得体,追求文章本身的古雅,简明优雅。用在接口上就是:

接口的信指的是接口准确、可信,说有什么功能就有什么功能,既实现了所有约定好的功能,又没有画蛇添足的多余功能,接口发布之后接口本身永远不做任何修改(功能当然是可以不断升级的),任何时候调用这个接口都是可信的;

接口的达指的是接口的名称和参数等内容,使用者一看就能知道是做什么的,任何人看了都不会引起歧义,任何人看到这个接口理解都是相同的,不会出现每个人理解不一致的情况;
理想的接口自带注释功能,使用者一看接口名称和参数,就能立刻知道这个接口提供的功能和使用的方法。

接口的雅指的是接口看上去要美观大方,简明优雅,简单的说就是卖相要好,用起来要方便,好接口使用者一看就放心,不好的接口别人看了就觉得不想用。
经常听到有人说,自己设计的接口的理念是优先考虑成本和稳定性,对于别人用接口的时候是不是觉得好用并不很在意,这个观点就是没有做到客户第一,如果有客户第一的想法,使用接口的人就是你的客户,那么设计出来的接口必然应该是好用的。
不专业的接口设计者,设计接口的时候只考虑自己写的爽不爽,不考虑别人用的爽不爽,而专业的接口设计者,会全面考虑各种问题,包括要考虑别人用的爽不爽。
对于上面这个观点再补充说明一下,接口的稳定性、成本,跟接口是否好用,二者并不是矛盾的,并不是说做到了这一点,另一点就没法做,其实一个接口的稳定性和成本是这个接口最起码的要求,根本就不值得单独拿出来说,而接口是不是好用,则直接关系到整个项目的研发成本,接口做到信达雅,整个团队的效率会更高,整个产品也会更稳定。
5.3 发布之后的接口永远不变
我们经常见到由于接口变化导致的各种问题,例如接口提供方修改了接口,调用方并不知道接口已经修改了,还是按照之前的约定使用,结果轻则出现调用不正确,重则出现各种崩溃问题。
开发阶段的接口变化在所难免,只要修改接口之前跟各使用方通个气即可,不过开发阶段如果接口经常修改,那说明这个设计接口的人肯定没有仔细思考,没有预判各种可能出现的情况,这个接口设计者的专业程度还是有很大提升空间的。但发布之后的接口,是无论如何不能再修改的,必须要无条件保持永远不变,所有接口向下兼容,这样能极大的降低由于版本不匹配造成的各种问题,产品要升级的时候,升级条件也可以简单很多,出错的可能性也随着降低很多。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值