如何规划我的软件以避免过度重写和相互依赖
我正在编写一个具有几个接口(按钮、蓝牙、触觉旋钮)的电机控制器,这是一项正在稳步增长的任务,其规模比我想象的要大。我尝试从低级模块开始(例如编写在 I2C 总线上通信的代码),然后是上面的模块(与 I2C 上的特定设备通信的代码)巴士...),但我常常不得不回到较低的模块来处理我无法适应的怪癖。这要么需要很长时间,要么我得到的代码非常糟糕。
我的目标是 8 位 MCU,因此自下而上似乎我可以更好地利用硬件。如果我自上而下,我就没有任何可以构建或测试/调试的结构。
我尝试绘制一些总体图和特定级别/驱动程序的图,但我不确定如何构建它们,这样我就可以非常系统地了解它,并避免错过需要经过 2-3 层的奇怪信号层。
我想这就是获得 CS 学位的原因吗?我是一名电气工程师 :P
I'm writing a motor controller that has a couple of interfaces (buttons, Bluetooth, haptic knobs) which is a task that is steadily growing to be a larger than I figured. I've tried to just go at it by starting with low-level modules (e.g. write code to talk on the I2C bus), then ones above that (code to talk to a particular device on the I2C bus...), but all too often I have to dive back down to my lower modules to handle quirks I didn't accommodate. This either takes a long time or I get really hack-ish code.
My target is an 8-bit MCU, so bottom-up seems like I can exploit the hardware better. If I go top-down I don't have any structure to build on or test/debug.
I've tried drawing up some overall diagrams and ones for particular levels/drivers, but I'm not sure how to structure them so I can be very systematic about it and avoid missing the odd signal that needs to go up through 2-3 layers.
I guess this is the reason for a CS degree? I'm an electrical engineer :P
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
听起来你走在正确的轨道上。有时,再多的计划也无法阻止您日后重新设计或重构系统的某些部分。尝试以下一些技巧:
It sounds like you are on the right track. Sometimes no amount of planning is going to prevent you from having to redesign or refactor parts of a system at a later date. Try some of the following tips:
在处理多层代码时,当 API 不允许您完全按照您的意愿行事时,您很容易会陷入较低层。当同时编写多层时,这尤其困难。
这里有一些建议:
When working in multi-tiered code, it's tempting to dive into a lower tier when the API doesn't let you do exactly what you want it to do. This is especially difficult when writting mulitple tiers at the same time.
Here's a few suggestions:
这实际上更多的是经验问题而不是你的学位问题。如果您仍在学习如何控制硬件,那么您的代码当然会发生变化。我不会为此烦恼。然而,由于您真正要做的是原型设计,因此您应该准备好在代码工作后重构代码。消除冗余,划分数据和功能,并组织您的界面,使其有意义。
我的经验是,设备驱动程序代码需要自上而下和自下而上的设计/实现,我称之为由外向内。您知道用户想要做什么,并且可以编写这些接口,并且您知道低级驱动程序需要做什么,并且可以编写这些接口。如果它们在中间没有很好地相遇,请重新考虑您的模块布局。
为了改进,请将您的设计和代码交给有更多经验的人来审查。把自我排除在外,了解他们对问题的看法。你还可以读一本关于面向对象分析和设计的书(我以前很喜欢Peter Coad的书,现在不知道是谁写的了)。一个好的例子将展示如何将问题划分为具有明确角色和职责的对象的示例。
一旦完成驱动程序原型设计并知道如何控制硬件,另一件事就是确保您有详细的要求。没有什么比在编写时发现需求更能扭曲代码了。在编写代码之前,您还可以尝试学习 UML 并使用图表进行设计。这并不适合所有人。另请注意,您不需要使用支持面向对象构造的语言来使用 OOD。
It's really more a matter of experience than your degree. If you are still learning things about how to control the hardware, then of course your code is going to change. I wouldn't agonize over that. However, since what you are really doing is prototyping, you should be ready to refactor the code once you have it working. Remove redundancies, compartmentalize data and functionality, and organize your interfaces so that it makes sense.
My experience is that device driver code needs top-down and bottom-up design/implementation, what I call outside-in. You know what the user is going to want to do and you can write those interfaces, and you know what the low-level driver needs to do and you write that. If they don't meet well in the middle, rethink your module layout.
For improvement, subject your design and code to review by people with more experience. Leave ego out of it and just get their take on the problem. You might also read a book on object-oriented analysis and design (I used to like Peter Coad's books. I don't know who's writing them now). A good one will show examples of how to partition a problem into objects with clear roles and responsibilities.
The other piece, once you have finished prototyping the drivers and know how to control the hardware, is to make sure you have detailed requirements. Nothing twists code more than discovering requirements while you're writing. You might also try learning UML and designing with diagrams before writing code. This doesn't work for everyone. Also note that you don't need to be coding in a language that supports object-oriented constructs to use OOD.
如果您的问题是如何构建正确的抽象(情况似乎如此),我认为您可以做的最重要的学习事情(除了要求设计代码审查/阅读书籍/阅读代码之外)是思考在开始编写代码之前先努力一下。
通常情况下,您首先会大致了解自己想要什么以及应该如何完成,然后继续编写代码。后来你发现你没有想清楚事情,并且有几个漏洞,现在,因为你投入了时间编写代码,所以很难修补这些漏洞,这会导致浪费时间或黑客代码。
认真思考如何创建一个可以轻松应对变化的设计。例如,封装需要在层之间传输的数据,因此,如果您后来发现错过了一个关键参数,您可以轻松地将其添加到结构中,而无需到处修改代码。
尝试多次“在头脑中运行设计”,直到您非常确定您已经考虑了最重要的细节并且您可以分辨出来(这是最重要的部分,因为您总是会错过一些事情,或者需求会发生变化)如果您错过了某些内容,您可以相对轻松地调整模块。
UML 可以帮助您构建思考设计的方式。它当然不是万能药,但它显示了创建软件设计时要考虑的不同标准。
这有点像经典的国际象棋老师的建议:“坐在你的手上”:-)
If your problem is how to build proper abstractions, which seems to be the case, I think that the most important thing you can do to learn (besides asking for design-code reviews/read books/read code) is TO THINK HARD BEFORE YOU START WRITING CODE.
It usually happens that you start with a rough idea of what you want and how it should be done and then go on to write code. Later on you discover that you didn't think things through and you have several gaping holes which now, because you have invested time in writing the code, are difficult to patch, which leads to either wasted time or hacky code.
Think hard about how to create a design that can easily handle changes. For example, encapsulate the data that needs to travel between layers, so if you later discover you missed a crucial parameter you can easily add it to the structure without having to modify code everywhere.
Try to "run the design in your head", several times, until you are reasoanly sure that you have considered most important details and you can tell (this is the most important part, because you'll always miss things or requirements will change) that if you missed something you can tweak the modules with relative ease.
UML can help you structure the way to think about the design. It's certainly no panacea but it shows the different criteria to consider when creating a software design.
It's a bit like the classic chess teacher advice: "sit on your hands" :-)
驱动程序适合分层方法。
您的驱动程序可以有几个“类”:
它们应该具有标准化接口,例如:
或者,另一种方法是
将参数/结果放入指定的寄存器中。
这是一种简单、可用的方法。更复杂的变体可能是在硬件通信代码和软件通信代码之间使用循环队列,而不是寄存器。
这是我正在使用的心理模型:
每层之间都有一个严格定义的、无差异的界面(我一直想象一个层间蛋糕,层之间有厚厚的糖霜......)。当然,您可能不存在该操作系统。
如果您的 MCU 支持软件和硬件中断(例如 x86 CPU),您可以使用它们将驱动程序与驱动程序通信器隔离。
老实说,这有点“过度设计”的解决方案。但是,在复杂性变得越来越大的情况下,采用紧密的工程比采用松散的工程更容易。
如果您在层之间进行通信,则可以为每个通信“通道”使用全局变量,并以规范的方式访问它,仅使用函数来访问它。
通常,在真正开始对项目进行编码之前,您需要在某种程度上进行纸张设计、一些探索性工作并重新设计。流程图和总线转换图在这里效果很好。
这是我在嵌入式系统工作中喜欢的方法,并且对我来说效果很好。
此外,传统的计算机科学课程并没有很好地探索这个问题空间。它比网络或现代操作系统要宽容得多。
Drivers are amenable to a layer approach.
Your drivers could have several "classes":
They should have a standardized interface, e.g.:
Or, another approach is to have something like
putting parameters/results in specified registers.
It is a simple, usable approach. A more complex variant might be to have circular queues between your hardware communicating code and your software communicating code, instead of registers.
Here is the mental model I am using:
There is a tightly defined, no-variance interface between each layer (I keep imagining a layer cake with thick frosting between layers...). The OS may not exist for you, of course.
If your MCU supports software and hardware interrupts like the x86 CPU, you can use those to isolate the drivers from the driver communicators.
This is a bit of an "overengineery" solution, to be honest. But, in a situation where your complexity is getting significant, it's easier to have tight engineering than to have loose engineering.
If you are communicating between layers, you can use a global variable for each communication "channel", and access it in a disciplined fashion, using only functions to access it.
Typically you will want to do paper designs at some level, some exploratory work, and redesign before you really set out to code your project. Flowcharts and bus-transition diagrams work well here.
This is the approach I've favored in my embedded systems work and it works well for me.
Also - this problem space isn't well explored by traditional computer science curricula. It's much less forgiving than the Web or modern operating systems.
在我看来,架构良好的代码最重要的方面是低耦合度和副作用与状态的分离。
另外——代码是一门手艺。不要以为从一开始就能制定出完美的解决方案。当您学习新事物时,准备好更改您的代码。事实上 - 确保您将自我改变作为工作的一部分。
In my opinion, the most important aspects of well architectured code are low degree of coupling and separation of side-effects from state.
Also - code is a craft. Don't think you can make the perfect solution from the beginning. Be prepared to change your code as you learn new things. In fact - Make sure that you embrace change it self as part of your work.
不要太油嘴滑舌,但我想起了《人月神话》中的一句话:“计划扔掉一个;无论如何你都会扔掉。”
其必然结果是“让它发挥作用。让它正确。让它快速。”
我想这让我提倡提前进行一些设计,但不要因此而陷入瘫痪。第一次不一定是完美的。计划重构。希望您所编写的内容不会真正丢弃太多代码,而是以更令人愉快的方式重新排列内容。
Not to be too glib, but a quote from The Mythical Man-Month comes to mind: "Plan to throw one away; you will anyhow."
The corollary of which is "Make it work. Make it right. Make it fast."
I suppose this makes me an advocate of doing some design up-front, but not getting paralyzed by it. It doesn't have to be perfect the first time through. Plan to refactor. Hopefully you will have written things in such a way that you're not really throwing much code away, but rearranging things in a more pleasing manner.