F#记录,用法,代码清晰度

时间:2012-10-04 01:33:10

标签: f#

背景:

我发现自己很多地利用了F#唱片。目前我正在开展一个分组解剖项目。重放专有的二进制协议(一种设计非常奇怪的协议......)。

我们定义数据包的骨架记录。

type bytes = byte array
type packetSkeleton = {
    part1 : bytes
    part2 : bytes 
    ... }

现在,很容易用它来“剖析”我们的数据包,(实际上只是给字节字段命名)。

let dissect (raw : bytes) =
  let slice a b = raw.[a..b]
  { part1 = slice 0 4
    part2 = slice 4 5
    ... }

即使对于冗长的数据包也是如此,如果切片有可预测的模式,我们甚至可以使用一些简洁的递归函数。

所以我解剖了数据包,拿出了我需要的字段,并根据我从解剖中获取的字段创建了一个基于packetSkeleton的数据包,现在开始看起来有点尴尬:

let createAuthStub a b c d e f g h ... =
   { part1 = a; part2 = b
     part3 = d; ...
   }

然后,在创建填充的存根之后,我需要将其反序列化为可以放在线上的表单:

(* packetSkeleton -> byte array *)
let deserialise (packet : packetSkeleton) =
  [| packet.part1; packet.part2; ... |]

let xab = dissect buf
let authStub = createAuthStub xab.part1 1 2 xab.part9 ...

deserialise authStub |> send

因此,最终我有3个区域,记录类型,给定数据包的记录创建以及反序列化的字节数组。有些东西告诉我,在代码清晰度方面,这对我来说是一个糟糕的设计选择,而且我已经可以感觉到即使在这个早期阶段也开始向我射击。

问题:

a)我是否为这样的项目使用了正确的数据类型?我的方法是否正确?
  b)我应该放弃尝试让这段代码感觉干净吗?

的 因为我有点通过触摸来编码,所以我会很感激一些见解!

P.S我意识到这个问题非常适合C,但是F#更有趣(另外在声音吸引人的情况下验证解剖器)!

2 个答案:

答案 0 :(得分:2)

如果数据包可能相当大packetSkeleton可能会变得难以处理。另一种选择是使用原始字节并定义一个读/写每个部分的模块。

module Packet
  let Length = 42
  let GetPart1 src = src.[0..3]
  let SetPart1 src dst = Array.blit src 0 dst 0 4
  let GetPart2 src = src.[4..5]
  let SetPart2 src dst = Array.blit src 0 dst 4 2
  ...

open Packet 

let createAuthStub bytes b c =
  let resp = Array.zeroCreate Packet.Length
  SetPart1 (GetPart1 bytes) 
  SetPart2 b resp
  SetPart3 c resp
  SetPart4 (GetPart9 bytes) 
  resp

这消除了对de / serialization函数的需要(并且可能有助于提高性能)。

修改

创建包装类型是另一种选择

type Packet(bytes: byte[]) =
  new() = Packet(Array.zeroCreate Packet.Length)
  static member Length = 42
  member x.Part1
    with get() = bytes.[0..3]
    and set value = Array.blit value 0 bytes 0 4
    ...

可能会减少代码:

let createAuthStub (req: Packet) b c =
  let resp = Packet()
  resp.Part1 <- req.Part1
  resp.Part2 <- b
  resp.Part3 <- c
  resp.Part4 <- req.Part9
  resp

答案 1 :(得分:1)

我认为你的方法本质上是合理的 - 当然,如果不了解更多细节,很难说清楚。

我认为在您的代码中显示的一个关键想法是功能架构的关键是类型之间的分离(用于建模问题域)和创建域模型值,处理它和格式的处理功能他们。

在你的情况下:

  • 类型bytespacketSkeleton为问题域建模
  • 函数createAuthStub处理您的域名(我同意Daniel的说法,如果以整个packetSkeleton作为参数,它可能更具可读性)
  • 函数deserialize将您的域恢复为字节

我认为这种构造代码的方式非常好,因为它分离了程序的不同问题。我甚至写了一篇文章试图将其描述为more general programming approach