3

我正在编写一个电机控制器,它有几个接口(按钮、蓝牙、触觉旋钮),这是一项稳步增长的任务,比我想象的要大。我试图从低级模块开始(例如编写代码以在 I2C 总线上通信),然后是上面的模块(与 I2C 总线上的特定设备通信的代码......),但是很多时候,我不得不回到我的较低模块来处理我没有适应的怪癖。这要么需要很长时间,要么我会得到真正的 hack-ish 代码。

我的目标是 8 位 MCU,所以自下而上似乎我可以更好地利用硬件。如果我自上而下,我没有任何结构可以构建或测试/调试。

我已经尝试为特定级别/驱动程序绘制一些整体图和一些图,但我不确定如何构建它们,因此我可以非常系统地进行处理并避免错过需要通过 2-3 的奇怪信号层。

我想这就是CS学位的原因吗?我是一名电气工程师:P

4

7 回答 7

3

在多层代码中工作时,当 API 不能让您完全按照您的意愿行事时,很容易潜入较低层。在同时编写多个层时,这尤其困难。

这里有一些建议:

  • 将除您正在处理的层之外的所有其他层视为已密封。由另一家公司、开发人员等创建。抵制修改另一层以解决当前层问题的冲动。
  • 为您正在处理的层创建一个“兄弟层”。这很难在抽象意义上描述,但是假设您的较低层是业务层,而较高层是 UI,则为不同的应用程序创建另一个 UI 层。现在拥有相同 API 的两个使用者可以帮助指出每一层应该是什么。
  • 尝试交替您在层级上工作的顺序。例如,在某些应用程序中,我发现首先设计 UI 更有用,然后再深入到业务层/数据库以使 UI 按设计工作。其他时候,使用数据模型统计数据并处理 UI 会更有意义。但关键是,在这两种情况下,您对 API 的“思考”不同。从两个角度查看多层代码会有所帮助。
  • 经验很重要。有时仅仅犯下过度耦合代码的错误是真正学会避免它的唯一方法。与其计划你的应用程序是完美的,不如计划它是不完美的。我的意思是首先建立一个快速的开发/测试/重构周期,这样你就可以快速适应在你犯错之前你不会看到的错误。这也是“一次性原型”派上用场的领域。做一个简单的草稿,从中学习,然后扔掉。扔掉的部分很重要。即使它令人惊叹,也请从头开始构建另一个。根据您从原型中学到的知识,您将不可避免地使它变得更好(并且根据我的经验,更有条理)。
于 2010-08-12T22:27:14.367 回答
3

听起来你在正确的轨道上。有时,再多的计划也不会阻止您在以后重新设计或重构系统的某些部分。尝试以下一些提示:

  • 将代码保存在由逻辑函数分隔的模块中。
  • 不要重复代码,而是为共享功能设计可重用的方法。
  • 尽量避免为特殊情况添加 hack 的诱惑。最终这将变得无法维护。相反,应尽快调整和重构小部分。最后尝试进行大规模的重新设计将更加困难。
  • 不要试图从一开始就过度设计系统,因为当您开始实际实施时,您可能只是在浪费时间。
  • 保持较低级别尽可能简单,然后在顶部构建更高级的功能。
  • 记录您的函数并编写一些单元测试,尤其是在添加复杂的条件语句之后。
  • 尝试在堆栈中尽可能高地捕获错误。例如进行输入验证和检查返回值。这将使调试更容易。
于 2010-08-12T23:07:53.820 回答
2

这真的比你的学位更多的是经验问题。如果您仍在学习如何控制硬件,那么您的代码当然会发生变化。我不会为此苦恼的。但是,由于您真正在做的是原型设计,因此您应该准备好在代码工作后对其进行重构。删除冗余,划分数据和功能,并组织您的界面,使其有意义。

我的经验是设备驱动程序代码需要自上而下和自下而上的设计/实现,我称之为由外而内。你知道用户想要做什么,你可以编写这些接口,你知道低级驱动程序需要做什么,然后你编写它。如果它们在中间不能很好地相遇,请重新考虑您的模块布局。

为了改进,请将您的设计和代码提交给更有经验的人进行审查。把自我排除在外,让他们对问题的看法。您可能还阅读了一本关于面向对象分析和设计的书(我曾经喜欢 Peter Coad 的书。我不知道现在是谁在写它们)。一个好的例子将展示如何将问题划分为具有明确角色和职责的对象。

另一部分,一旦你完成了驱动程序的原型设计并知道如何控制硬件,就是确保你有详细的要求。没有什么比在编写时发现需求更能扭曲代码了。您也可以在编写代码之前尝试学习 UML 并使用图表进行设计。这并不适合所有人。另请注意,您无需使用支持面向对象结构的语言进行编码即可使用 OOD。

于 2010-08-12T22:27:25.910 回答
2

如果您的问题是如何构建适当的抽象,这似乎是这种情况,我认为您可以做的最重要的事情是学习(除了要求设计代码审查/阅读书籍/阅读代码)是在您之前认真思考开始编写代码

通常情况下,您首先对您想要什么以及应该如何完成有一个粗略的想法,然后继续编写代码。后来你发现你没有考虑清楚,你有几个大漏洞,现在因为你在编写代码上投入了时间,所以很难修补,这要么浪费时间,要么导致代码乱七八糟。

认真思考如何创建可以轻松处理更改的设计。例如,封装需要在层之间传输的数据,因此如果您后来发现遗漏了一个关键参数,您可以轻松地将其添加到结构中,而无需在任何地方修改代码。

尝试“在脑海中运行设计”,多次,直到你确信你已经考虑了最重要的细节并且你能说出来(这是最重要的部分,因为你总是会错过一些事情或需求会改变)如果你错过了一些东西,你可以相对轻松地调整模块。

UML可以帮助您构建思考设计的方式。它当然不是灵丹妙药,但它显示了创建软件设计时要考虑的不同标准。

这有点像国际象棋老师的经典建议:“坐在你的手上”:-)

于 2010-08-12T22:39:58.223 回答
1

驱动程序适用于分层方法。

您的驱动程序可能有几个“类”:

  • 仅输入
  • 仅输出
  • I 和 O。

他们应该有一个标准化的接口,例如:

GetKnobLevel()
GetNextKeyboardButton

或者,另一种方法是有类似的东西

syscall(DRIVERNUMBER, command)

将参数/结果放在指定的寄存器中。

这是一种简单、可用的方法。一个更复杂的变体可能是在硬件通信代码和软件通信代码之间使用循环队列,而不是寄存器。

这是我正在使用的心理模型:

---
Application
---
OS
---
Driver communicators
---
drivers
---
hardware

每层之间都有一个严格定义的、无差异的界面(我一直在想象一个层间蛋糕,层间有厚厚的糖霜……)。当然,操作系统可能不适合您。

如果您的 MCU 支持 x86 CPU 等软件和硬件中断,您可以使用它们将驱动程序与驱动程序通信器隔离开来。

老实说,这有点“过度工程”的解决方案。但是,在您的复杂性变得越来越重要的情况下,采用紧密的工程比采用松散的工程更容易。

如果您在层之间进行通信,您可以为每个通信“通道”使用一个全局变量,并以规范的方式访问它,只使用函数来访问它。

通常,在您真正开始编写项目代码之前,您会希望在一定程度上进行纸质设计、一些探索性工作并重新设计。流程图和总线转换图在这里工作得很好。

这是我在嵌入式系统工作中所青睐的方法,它对我来说效果很好。

另外 - 传统的计算机科学课程没有很好地探索这个问题空间。它远没有 Web 或现代操作系统那么宽容。

于 2010-08-12T23:12:52.600 回答
0

在我看来,良好架构的代码最重要的方面是低程度的耦合副作用与状态的分离

另外 - 代码是一门手艺。不要以为你可以从一开始就做出完美的解决方案。准备好在学习新事物时更改代码。事实上——确保你接受改变自己作为你工作的一部分。

于 2010-08-12T22:59:31.063 回答
0

不要太油嘴滑舌,但我想起了The Mythical Man-Month的一句话:“打算扔掉一个;无论如何你都会的。”

其推论是“让它发挥作用。让它正确。让它快速。”

我想这使我提倡预先进行一些设计,但不要因此而瘫痪。第一次完成不一定是完美的。计划重构。希望您的编写方式不会真正扔掉太多代码,而是以更令人愉悦的方式重新排列事物。

于 2010-08-16T16:10:06.743 回答