nspcc-dev / neofs-sdk-go

Go implementation of NeoFS SDK
Apache License 2.0
6 stars 14 forks source link

Provide constructors for type instances #483

Open cthulhu-rider opened 1 year ago

cthulhu-rider commented 1 year ago

there are several reasons, one of them https://github.com/nspcc-dev/neofs-sdk-go/pull/479#discussion_r1279235656

currently, we almost always initialize type instances via methods:

var t T
t.SetField(f)

one of the obvious disadvantages of this approach is the need to explain in the docs (and hope that a beginner will read it) mandatory methods, without which the instance remains invalid

one of the obvious advantages is readability:

var t T
t.SetEpoch(10)
t.SetCool(true)

// is clearer than
t := New(10, true)

it's proposed to provide constructors - functions with required parameters. For example, for signatures:

func New(signer Signer, data []byte) (Signature, error)
func NewSignatureFromV2(msg refs.Signature) (Signature, error)
func NewSignatureFromBytes(b []byte) (Signature, error)

with this Calculate / ReadFromV2 methods won't be needed (their use will remain in the reuse of the instance, but in practice this is rarely necessary)

t := New(a,b,c)
t.SetD(d) // optional
t.Sign(signer)

send(t)

// T must be signed.
func send(t T)

as we can see, we still need to write docs like must be signed and hope user will follow them. We can try to go further and make constructors finalizers, but that would require subtypes:

type BlankT struct{}
type T struct{}

t := NewBlank(a,b,c)
t.SetD(d) // optional

st := New(t BlankT, signer neofscrypto.Signer) (T, error)

func send(t T) // accepts signed entity by design

or with different naming

type T struct{}
type SignedT struct{}

t := New(a,b,c)
t.SetD(d) // optional

st := NewSigned(t BlankT, signer neofscrypto.Signer) (SignedT, error)

func send(t SignedT) // accepts signed entity by design
cthulhu-rider commented 1 year ago

we may also provide decoding constructors

func NewFromBinary(b []byte) (T, error) {
  var t T
  return t, t.Unmarshal(b)
}

func NewFromJSON(j []byte) (T, error) {
  var t T
  return t, t.UnmarshalJSON(j)
}

and for some types

func NewFromString(s string) (T, error) {
  var t T
  return t, t.DecodeString(s)
}

could replace 2 instructions with 1 when there is no instance yet