软件设计
Posted 下士闻道
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了软件设计相关的知识,希望对你有一定的参考价值。
- 软件设计
- 一定是创建订单的时候填充market字段,我曾经一度打算在回调的时候再根据回调方来填充Market,但是如果没有回调呢?Market这样的标志性字段一定要依赖于靠谱的操作;
- 对于重载方法要注意,尤其套调用的重载方法,对于某些核心校验必须要放置在里层方法调用,否则因为重载都是public出去的,都可以被外界调用,如果在外层方法实现校验,里层重载方法被外界直接调用,校验会被跳过;考虑CheckMarket是放在CreateOrder(String encryptedString)还是CreateOrder(OrderInfo orderInfo)中实现?前者解析加密字符串调用后者,最后想通了是要放在后者中进行校验;
- 设计的时候一定要在设定的场景中多走几遍,这样做的目的是跳出实现,要把实现放在应用中进行验证;本质:任何实现都是一个流程的一个环节,其意义在于上下游,而不是实现本身;
- 获取玩家信息其实使用旧有的接口即可,但是我却搞混了玩家信息表和订单表;分析问题就是要极端:向上谋求大的方向,向下到根(表,字段级别),谋求准确;
- 腾讯之所以二次验证是因为:担心回调返回信息没有接收到,没有扣款;这个机制不同于其他平台,其他平台如果没有收到,将会续发;腾讯这样处理其实是把球抛给了游戏服务器;
- OpenId已经修改为String类型真的是百搭啊,作为众多市场使用的字段,int的范围还是太小了;这个让我想到了CreateOrder的返回值,对于适配众多情况的字段、方法类型一定要考虑百搭的类型;Factory入口参数已经由枚举改为了字符串类型,因为我发现了一个问题,需要枚举和显示字符的互转,这样每增加一个市场,同时需要维护枚举和常量,太不靠谱了;这样看来单独一个类里面封装常量其实是比枚举具有更大的描述能力;CreateOrder之初设计的返回int,这个返回值太小了,包含的信息量远远不够,尤其是对于支持众多市场;比如面对腾讯的支付机制,返回的就是一个JSON串,所以对于通用的接口设计返回值的格式要复杂一些,描述能力强一些;
- 支付市场流程中,支付服务器回调游戏服务器,其实主要的用意是通知游戏服务器添加金币,增加道具;
- 我已经修改为统一使用BasePaymentInfo作为参数,这样的替换大大减少了代码,之前需要字典对参数进行接收,然后再PaymentService在取出参数进行处理;
- 其实很多时候一个空的基类,作用就是用于实现开闭原则的闭,BasePaymentInfo里面什么都没有;但是BaiduPaymentInfo以及QihuPayemntInfo都是继承自它;
- 前天我在和Steven在谈论是否需要把支付拿成一个dll的时候,Steven讲到他可以保证部署的时候不出错;但是这样上纲上线有意义吗,因为是否就是保证修改代码的人一定能够取到最新的代码,是否部署的人一定就是Steven?最小的部署就是能够尽量不要依赖于人的因素,或者尽量减小人的因素;
- PaymentService的价值在于对于支付这个领域提供了一层的封装,对于外界的调用而言只需要知道一个Pay接口就可以了,而不需要知道还有一个Factory,还有一个Manager;
- 今天测试发现了一个Bug,更新Bill表之后直接操作更新了PlayerData表,这里有个问题:如果Bill表查到的数据为空,是不需要更新后者的,结合另外一个Bug,支付失败了是不需要更新PlayerData的;其实这两步操作不是代码关联,其实是:两步操作,牵涉到了两部分业务(逻辑),这种跨域的操作一定是要有一个分析和校验过程,是否具备关联性;这两个问题提供了一个视角:你再来做项目不要只是看到代码,而是要抽象到一个更高的角度,从"模块化"(业务、逻辑)以及全流程来看待开发本身;
- 创建订单等操作还是要放置在BasePaymentManager,而不是Service,因为创建订单逻辑可能是会变得,你需要让PaymentManager的实现类能够重写;如果放置在Service里面就不好处理了;其实开闭原则,讲的是对变化开放,对稳定关闭,换句话讲,就是对于开放接口是关闭,内部实现是开放,比如对于Service(领域入口)必须是关闭的,需要设计参数都是具有一定的伸缩性,但是对于内部实现,manager是哪个,应该具有扩展性;
- 对于接口的伸缩性,比如服务器增加了一种情况为,有的客户端可能需要改代码,享受新增加的功能,有的客户端就不需要改代码,旧有的功能就可以了,这个时候接口标准是不变的,不需要享受的也不需要改动;保证了请求多样性;
- 对于sessionKey的保存最开始打算保存在redis中,但是动物快跑,水浒传等牙根就没有Redis,另外,sessionKey是一个很重要的变量(Confirm环节需要拿去和支付服务器做支付确认的),不是那种丢了就丢的重要信息,一定是要保存在数据库中的;我添加了一个EXT_INFO字段用于存放sessionKey信息;
- 微信支付Demo中的WXPayData是键值对形式(封装了toXml,toJson等方法),里面包了一个SortDictionary方法;这种数据结构扩展性很好;很大程度上可以和空基类相提并论;缺点是无法进行编译时报错;
- 微信平台公共平台的开发因为无法调用外网;于是我把获得路径封装为一个方法,如果是测试环境(根据配置文件数据),我将会返回一个模拟功能的ashx文件进行数据返回;这样可以测试下去了;
- 在非Web环境下,Controller的session对象为空;这个时候为了能够进行单元测试,我设计了一个CaseBase类,这个类会根据配置文件配置,测试环境下(Test工程跑步),就采用内置Diction进行数据保存;正式Web环境下就采用session进行保存;为了保证全局可访问,不随着controller消亡而消失,Diction对象需要声明为static变量;
- 说到了单元测试,我觉得在biz层的设计一定要和前端的神马(Web技术)技术解耦,就是一个biz框架,提供了那些功能,Web也好remoting也好,还是Webservice都是可以直接调用;这个很独立的第三方;
- 作为类库,可以考虑返回值为OperationResult,内部发生异常不要抛出来,让调用方判断是抛出还是过去;
- 单元测试如果测试的机制和待测试代码机制一样也就失去了测试了意义;
- 梳理清楚业务:1.调用的场景;2.操作了什么表;3.过得了什么信息(字段)4.整体流程是怎样的;
- 掌握一门技术,要首先搞懂几个问题:1.这个技术解决了什么问题;2.优势是什么,适合什么场景;3.缺点是什么,不适合什么场景;4.提供功能的原理是怎样的;才算是了解了一门技术;
- 架构师的世界:一切尽在掌握,对于各个生命周期阶段都能够切入和控制;这样便于扩展,这就需要两种工具:封装和隔离(比如MVC很大的好处就是可以控制view,比如我想要把view缓存,就可以通过在contrroller里面做,但是传统的ASP.Net如果想要对view缓存就不是很直观;这也是为什么要封装日志接口;Session接口;比如serverlet是被tomcat的standwrapper封装而不是直接使用;归拢,核心系统套一个壳子隔离和第三方;为什么过期自动删除redis很少的应用场景,因为删除很多时候需要业务逻辑判断以及处理;所以很多时候采用的判断时间戳,到期了再如何如何;
- 今天在使用印象笔记发现一个问题,搜索之后,用户不知道怎么回到初始状态,其实我们做系统设计很多时候都会忽略行为的回朔,就是任何动作之后,都要让用户能够很容易回到某种状态,至少应该是回到初始状态,最后还好,在左侧菜单找到了"笔记"的图标,返回了初始页面,但是这个图标并不明显,不论怎样,evernote还是考虑到了这一点,搞了一个总回头的图标恢复状态;
- 需求驱动架构,架构驱动架构,架构驱动测试,测试驱动开发;
- 测试用例的设计,做到一个方法只测试一个点;
- 对入口参数校验真有意义,比如buildcommand方法,如果传入的ceb的identifier和command不一致,报错,问题一下清晰了;
- 刚才纠结于getdatabuf是否要如果null返回一个new对象,后来发现不需要:就像c++遭人骂就是因为行为里面做了太多的事情,职责清晰,尽量简单;
- 框架,是自成体系,只不过有些缺失部分,需要进行填充,然后框架就成为了系统,系统,是流程,生命周期完备的东西;
- 开发每天至少应该有39%的时间是在思考,学习;
- 架构/框架的本质是定义行为,定义行为的前提是定义好模块以及模块功能,让这些模块通过组合能够完成系统的功能;
- 关系,关系,关系!做设计很重要的一点就是理清楚关系,从session池的处理(连接到同端的连个session怎么区分),到通知参数(多个文件情况如何通知应用),都在说明,设计就是要捋顺对象间关系,面向对象就是构建世界,貌似简单,但是世界对象间很多关系是隐含的,并不显式,有些关系可以忽略不用构建,考虑,但是有些确实要挖掘。
- 框架思维,何为大者,就是看到的点是大的方面,对于系统的理解和开发过程都要放到一个框架思维来进行设计
- 什么是框架?框架就是一种机制,保证某些维度功能的机制(比如开发效率,性能,隔离原则等);
- 每当你在设计缓存的时候,考虑数据丢失是否可以,是否有机制重新获取,这种机制是否实现;
- 其实一件事情没有做完就开始下一件事情,寄托于未来完善,可以,但是一定是一个成品,而不是概念品,因为,你未来再做这件事情最大的成本就是,你需要重新进行梳理思路。续传就是这样,单元测试做完了,没有结合测试,回过头来再完善,发现思路全忘了,还要重头来;
- 每当你引入一个新的对象,比如任务的临时ID,都要考虑他的生命周期,以及他的生命周期和别的对象生命周期之间的影响,其次要把生命周期和事件捋顺了,比如任务的生命周期是INIT-RECEIVING-REVCOMPLETE-SENDING-SENDCOMPLETE,其中INIT是发生在DATA2Handler,Receving是发生在DATA指令等。
- 认证,用意就是证明这个用户存在,授权,就是这个用户可以干这个事情。
- 都是提供功能,和业务逻辑相关的称之为服务,和业务逻辑无关的称为基础设施
- 做架构设计一定要从重要的,重点的地方设计,否则你的设计很可能就卡在这些地方。比如当时对于断点续传考的不全面,直接导致了开发到了后面的问题;
- switch有一个缺点,容易忘记break, 而且如果多了,其实结构并不清晰,还不如else if;
- string有一个巨大的问题,就是他其实是一个弱类型,首先比较容易出错,是用==还是用equals,其次复制了一个常量,忘记改名字了,除非运行时测试,否则不自知。这一点是枚举的好处。
- PraCommand处理的时候需要知道发送方是否就是目的端。可以有两个地方进行处理,目的端发现自己是目的端,则更新PraCommand的isTail字段;第二个地方是接收方来判断,发送方是否和路由的最后一个设备一致。后来我选择的是前者,这是因为这样能够有效的减少判断;比如作为转发端,转发十个端那么判断十次;但是作为目的端而言其实只需要判断一次即可。这是一种分压/分流的机制。
- 想好了,一气呵成写,减少手敲,增加拷贝,那个写perl的哥们,c++代码写的时候就是绝大多数是拷贝
- 技术
- 自定义异常很大程度上解决了测试异常的问题;比如现在使用resultCode来判断错误类型,如果使用异常的话完全可以利用Exception进行处理;另外.net的ExceptedException真的好弱啊,只能判断类型,Message都指定不了,难道只能try...catch中增加assert吗?
- 多线程开发,对于每一个线程而言,代码都是独立的,他们不过是按照顺序去执行附加的代码;但是代码段本身是共享的,这里系统会为每个线程分配自己的参数堆栈,如果是代码段内部的变量(局部变量),各自堆栈维护,不会有任何问题;操作有交集,但是操作数据都是自己的数据;这个就像过去的合厨做饭一样,炉子是公用的,但是锅碗瓢盆,菜果都是自己的,及时彼此做饭有交集,用的都是公用的炉子,空间,但是做出来的都是自己的饭菜;但是,对于共享的变量就不一样的:因为他会变,而且不会因为你的操作而变,比如合厨的液化气,你用完之后,第二次再用存量就会少,因为别人(别的线程)也会用;所以,当你想要对这种公共变量操作的时候,为了保证操作间原子性,需要为操作加锁(锁上合厨的门);
- Buddy描述的关系,并不一点是要好友才使用SFS中的Buddy,能够用Buddy的关系来描述的都可以使用它;
- 8bit = 1byte;但是位(bit)是计算概念,二进制运算;但是byte是表示概念,一个byte表示可以容纳一个ascii码值,2个byte可以表示为容纳一个汉字;
- 1KB = 1024byte,1GB = 1024KB;bit和byte是8进制,但是B到KB,KB到GB则是10+3E进制(1024);
- USE_FLG价格索引后,查询效率立即提高了;
- 证书加密的本质:服务器交给客户端一把钥匙一把锁;客户端将会使用这把钥匙来打开服务器端发送的"黑匣子",客户端会用这把锁来锁住"黑匣子"然后寄给服务器端;客户端同样也会把一把钥匙和锁头给服务器端;
- 构建一个性能解决方案列表:insert而不用update;DB迁移到memcache;EF中SaveChange导致性能下降,那么就采用存储过程;查询昵称导致性能问题,那么就一次性把ID导入到内存中来;
- ASCII中有空"",代码是0,这也是为什么字母排序机制中,"逐位比较"机制中,"aaa"是要小于"aaaa"的原因,到第四位进行比较的时候,前者是空,ASCII值为0,后者是"a",值为65;备注,0的ASCII值为48;
- MIME(Multipurpose Internet Mail Extensions)最初是应用在电子邮件上面,可以根据MIME信息进行指定应用程序打开传输内容;后来这种技术被浏览器扩展,早期的浏览器是不支持传输非ASCII内容的;后来扩展了MIME技术到浏览器上面,这样就可以通过HTTP协议进行传输各种应用类型文件,浏览器将会根据MIME信息安排指定的应用程序打开/运行;比如文本文件text/html,pdf文件application/pdf,浏览器看到了application/pdf就会使用内嵌的pdf插件进行打开;调查MIME的由头是application/octet-stream类型,后来这种MIME类型是任意二进制文件;
- SCSI: Small Computer System Interface,定义了系统和设备交流的标准,比如和硬盘,CD-ROM,另外一种常用的接口常见的接口是IDE;
- cpu版本x86是32位,x86_64则是intel为了兼容AMD的64位指令而设计的;x64则是AMD的架构了;IA-64让Intel输惨了;Windows2008本来答应开发基于IA-64的操作系统,结果很久都没有完成;
- 负载均衡有两种,均衡操作,集群中的读写分离就是这种均衡(基于主从服务器);还有一种是访问压力均衡,比如一致性算法,nginx的IP_Hash等;
- 设置为cache-control的max-age以及Expire关键字之后,在各个浏览器张行为一致的是通过页面点击链接二次链接会缓存页面,以及新的Tab页面输入同样地址后发送请求,都将不会触发二次请求,直接用本地缓存文件;对于F5刷新,各个浏览器都是每次都从服务器取值;对于IE和firefox而言,在地址栏敲回车不会因为二次请求,chrome依然会进行二次请求;使用firefox之前各种情况都会向服务器发送请求,后来诊断确定是因为配置中将缓存空间设置为0导致;
- 另外一种相关的配置是LastModified,这个指令是response的时候,服务器给客户端的,这样下次向同样的资源发出请求的时候,将可以直接返回304告知客户端什么都没有改变,这样就可以放心大胆的使用客户端缓存内容了,但是在和业务后台相关的处理中,需要代码来判断是否有改变,需要controller实现LastModified接口,并实现方法进行业务判断;
- Ctrl+F5,强制重服务器取出新的内容;其实就是在Request头中添加了Programa:no-cache;Cache-Control:no-cache;其实你如果使用FF就会发现,如果设置缓存空间为0,那么无论你是地址栏敲回车,还是页面链接,F12里面的"网络"面板都会感应出请求信息,但是一旦你设置了空间,就会在该面板中显式要你刷新;这就说明了前面的情况是因为根本就没有向服务器发出请求,走的本地缓存;
- Keey-alive允许了客户端发送请求后,并不马上断开Http通道(TCP通道),多个Http请求公用一个TCP通道很大程度上减轻了服务器为每个请求创建进程处理的成本(另外还有三次握手,Http是基于TCP的);因为一个Html,除了页面元素,还有css,js都是要通过http进行多次请求完成的;但是keep-alive还有一个缺点,就是如果不能及时释放将会导致资源浪费;所以Keep-alive一般都是指定timeout和max,max指的是一个通道最多能够接受的请求;timeout指的是断开连接的时长;如果在timeout时长内请求数达到了max上限,将自动断开连接;
- 在hosts文件中看都有一个注释掉的::1,这个其实是代表ipv6地址,ipv6采用128位进行表示,格式为8个四位16进制组成,1234:5678:90abc:def0:1234:5678:90ab:cdef,为了兼容Ipv4地址,ipv4地址将被表示为0000:0000:0000:0000:0000:0000:192.168.0.1(ipv4地址占据两段,这意味着ipv4是32位的);对于前面的六段四位0,可以简单表示为::192.168.0.1;所以你看到的hosts文件中的::1其实就是为ipv6地址做准备的;
- "()"在正则表达式中代表模式,前面可以是一堆^*.,但是真正闪亮登场的确是"模式",模式代表着真正要匹配的部分,对于replace方法而言,要替换的就是模式部分,如果表达就是模式可以省略()
- 在Java里面小心转意需要使用\\,因为字符串里面内容才是表达式,比如替换掉所有括号里面内容replaceall("\\([^(]*\\)",""),或者在表达式中前后添加()也可以,语义更加准确,表示模式
- 使用filebuffer来封装hasHMAP<string, queue<commandexchangbuffer>>,但是后来发现这种封装损失了对于key的遍历好处,判断某个是否存在还需要遍历,而不是contains,十分不优雅;
- xmlns:xsi是指web.xml遵守xml规范;xsi全名:xml schema instance;
- xsi:schemaLocation是指具体用到的schema资源, 是命名空间和xsd文档配对出现,校验xML是否合法,就是到此获得xsd文件,对节点属性进行check的;如果你把xml:context删掉了,spring将会做xML文档校验,对于<context>节点就无法进行解析,编译将会出错;
- spring多做了一点,如果你的xsd文件没有指定版本号,那么就不从网址下载,而是从本地的spring的jar文件中,找相应的文件进行处理;避免因为网络原因无法获得xsd文件而启动失败
- spring拦截器,通过mapping以及exclude-mapping,能够指定那些请求需要拦截,那些请求不需要拦截;拦截器需要指定处理bean,对于拦截的请求,交给bean进行处理。
- 在spring的配置文件头中指定了很多命名空间;
- 事务的acid
原子性,账户转移,A账户转到B账户,a转了10元,B一定会接收到10元,要么成功,要么全失败;
一致性,A少了10元,B多了10元;
独立性,原子操作未完成,B在操作过程中是不会发现事务过程;
持久性,对于修改是持续可见的。
- 我现在更加发现抽象美妙之处,使用niosession,完美解决tcpsession以及datagRAMsession问题。
- 调错于其他
- 今天使用log4Net,每条记录都被打了两条,我的第一反应是代码执行了两边,但是其实是因为log4Net配置的问题,配置了两个logger对象(root以及一个自定义的logger对象);
- 状态,当初不应该图省事采用commanidentifier,应该重新定义一套定义,语义还是不太一样
以上是关于软件设计的主要内容,如果未能解决你的问题,请参考以下文章