Skip to main content
added 2036 characters in body
Source Link
JimmyJames
  • 31.1k
  • 3
  • 59
  • 111

It seems upon updates by the OP that I glommed onto the wrong part of the text and the CQS aspect of this is not terribly relevant to the real question. I'm going to leave the original answer since it was upvoted and might be useful to others.

As I understand it now, you are asking about whether it is important to wrap your properties in methods as is common in Java code. I don't think there's a simple yes or no answer. I would also offer that the really important questions are whether your state is encapsulated sufficiently and whether your components are too tightly coupled.

I make that distinction because while using methods to access properties can be useful in encapsulating state, wrapping a property in a 'get' method doesn't necessarily encapsulate state effectively.

As an example of this, consider a JS const array variable. No one can change the value of the variable even if it is public. But that in no way prevents the array from being modified. Any part of your code can modify it. You can make it private and provide an access method but that doesn't prevent external modification of the array either. In both cases you are still giving direct access to the underlying array which is part of the state. And if you set that array reference to one passed in from elsewhere, that source still can modify the array and change the state of the object.

I'm not very familiar with TS but it seems you have this kind of situation in your second code snippet e.g. public readonly address: Array<number>

To address this, there are a couple of options. You can freeze an array that is exposed as readonly. This might be a good option if you never want the contents of the array to change. If you want your object to be the only thing that can change the array, you can use an accessor method and use defensive copies (on initialization and in the accessor) to prevent other parts of the code from making changes to the array which makes up part of your objects internal state.

Original Answer

As noted in the comments, this idea is related to the principle of Command-Query Separation (CQS). We can break this into two parts:

As noted in the comments, this idea is related to the principle of Command-Query Separation (CQS). We can break this into two parts:

It seems upon updates by the OP that I glommed onto the wrong part of the text and the CQS aspect of this is not terribly relevant to the real question. I'm going to leave the original answer since it was upvoted and might be useful to others.

As I understand it now, you are asking about whether it is important to wrap your properties in methods as is common in Java code. I don't think there's a simple yes or no answer. I would also offer that the really important questions are whether your state is encapsulated sufficiently and whether your components are too tightly coupled.

I make that distinction because while using methods to access properties can be useful in encapsulating state, wrapping a property in a 'get' method doesn't necessarily encapsulate state effectively.

As an example of this, consider a JS const array variable. No one can change the value of the variable even if it is public. But that in no way prevents the array from being modified. Any part of your code can modify it. You can make it private and provide an access method but that doesn't prevent external modification of the array either. In both cases you are still giving direct access to the underlying array which is part of the state. And if you set that array reference to one passed in from elsewhere, that source still can modify the array and change the state of the object.

I'm not very familiar with TS but it seems you have this kind of situation in your second code snippet e.g. public readonly address: Array<number>

To address this, there are a couple of options. You can freeze an array that is exposed as readonly. This might be a good option if you never want the contents of the array to change. If you want your object to be the only thing that can change the array, you can use an accessor method and use defensive copies (on initialization and in the accessor) to prevent other parts of the code from making changes to the array which makes up part of your objects internal state.

Original Answer

As noted in the comments, this idea is related to the principle of Command-Query Separation (CQS). We can break this into two parts:

As noted in the comments, this idea is related to the principle of Command-Query Separation (CQS). We can break this into two parts:

  1. queryingQuerying (getting) an object shouldn't change its state.
  2. Updating a value should not return it'sits state.

I would say that the first is a pretty rock-solid rule. You are just asking for problems if you violate this. Maybe there's an exception but I can't think of one on a Monday.

As far as the second one goes, there are a number of reasons you might want to ignore that, or at least not return void:

Method Chaining

It's common in contemporary code to return this/self instead of void/None. This allows you to write code like this:

object.foo(x).bar(y) instead of:

object.foo(x) object.bar(y) 

I would argue that this still follows the spirit of CQS because the return is not really querying the object. However, this approach also often involves factory methods where an object will create a new child object and return it. This would seem to violate CQS but I have no issue with it and I think it's much better than the alternatives.

Success Status

Some APIs I've worked will do things like return a boolean flag indicating whether the call resulted in a change. For example, adding to a set will return true if the set was changed and false if the item was already in the set. This is useful when you want to take a one-time action upon a new value. Similarly, some map/dictionary APIs will return the prior mapped value on an update. Doing this with two calls might create flaws (e.g. TOCTOU) in some contexts such as discussed in the next section.

Atomicity

I would argue that the most clearly appropriate reason to ignore CQS (for updates) is when you can't guarantee that some other change will not occur between a read and a write. For example, lets say you have a multi-threaded routine which adds items to a list. Each thread adds an item and then if the number of items and fire a notification on each hundredth item. If you need to do this in two calls, there's a potential issue: thread A adds an item making the total 500. Before thread A can get the total count, thread B adds an item making the total 501. Then both thread A and thread B get the count as 501. Therefore no event is fired on the 500th item.

To solve this under a strict CQS regime, you would need some sort of locking mechanism preventing other threads from making changes between calls. Aside from being complicated and error prone, it can create an issue with contention. If instead, you have that add method return the count of items, you can allow the method to handle thread safety internally with a much broader array of approaches.

As noted in the comments, this idea is related to the principle of Command-Query Separation (CQS). We can break this into two parts:

  1. querying (getting) an object shouldn't change its state
  2. Updating a value should not return it's state

I would say that the first is a pretty rock-solid rule. You are just asking for problems if you violate this. Maybe there's an exception but I can't think of one on a Monday.

As far as the second one goes, there are a number of reasons you might want to ignore that, or at least not return void:

Method Chaining

It's common in contemporary code to return this/self instead of void/None. This allows you to write code like this:

object.foo(x).bar(y) instead of:

object.foo(x) object.bar(y) 

I would argue that this still follows the spirit of CQS because the return is not really querying the object. However, this approach also often involves factory methods where an object will create a new child object and return it. This would seem to violate CQS but I have no issue with it and I think it's much better than the alternatives.

Success Status

Some APIs I've worked will do things like return a boolean flag indicating whether the call resulted in a change. For example, adding to a set will return true if the set was changed and false if the item was already in the set. This is useful when you want to take a one-time action upon a new value. Similarly, some map/dictionary APIs will return the prior mapped on an update. Doing this with two calls might create flaws (e.g. TOCTOU) in some contexts such as discussed in the next section.

Atomicity

I would argue that the most clearly appropriate reason to ignore CQS (for updates) is when you can't guarantee that some other change will not occur between a read and a write. For example, lets say you have a multi-threaded routine which adds items to a list. Each thread adds an item and then if the number of items and fire a notification on each hundredth item. If you need to do this in two calls, there's a potential issue: thread A adds an item making the total 500. Before thread A can get the total count, thread B adds an item making the total 501. Then both thread A and thread B get the count as 501. Therefore no event is fired on the 500th item.

To solve this under a strict CQS regime, you would need some sort of locking mechanism preventing other threads from making changes between calls. Aside from being complicated and error prone, it can create an issue with contention. If instead, you have that add method return the count of items, you can allow the method handle thread safety internally with a much broader array of approaches.

As noted in the comments, this idea is related to the principle of Command-Query Separation (CQS). We can break this into two parts:

  1. Querying (getting) an object shouldn't change its state.
  2. Updating a value should not return its state.

I would say that the first is a pretty rock-solid rule. You are just asking for problems if you violate this. Maybe there's an exception but I can't think of one on a Monday.

As far as the second one goes, there are a number of reasons you might want to ignore that, or at least not return void:

Method Chaining

It's common in contemporary code to return this/self instead of void/None. This allows you to write code like this:

object.foo(x).bar(y) instead of:

object.foo(x) object.bar(y) 

I would argue that this still follows the spirit of CQS because the return is not really querying the object. However, this approach also often involves factory methods where an object will create a new child object and return it. This would seem to violate CQS but I have no issue with it and I think it's much better than the alternatives.

Success Status

Some APIs I've worked will do things like return a boolean flag indicating whether the call resulted in a change. For example, adding to a set will return true if the set was changed and false if the item was already in the set. This is useful when you want to take a one-time action upon a new value. Similarly, some map/dictionary APIs will return the prior mapped value on an update. Doing this with two calls might create flaws (e.g. TOCTOU) in some contexts such as discussed in the next section.

Atomicity

I would argue that the most clearly appropriate reason to ignore CQS (for updates) is when you can't guarantee that some other change will not occur between a read and a write. For example, lets say you have a multi-threaded routine which adds items to a list. Each thread adds an item and then if the number of items and fire a notification on each hundredth item. If you need to do this in two calls, there's a potential issue: thread A adds an item making the total 500. Before thread A can get the total count, thread B adds an item making the total 501. Then both thread A and thread B get the count as 501. Therefore no event is fired on the 500th item.

To solve this under a strict CQS regime, you would need some sort of locking mechanism preventing other threads from making changes between calls. Aside from being complicated and error prone, it can create an issue with contention. If instead, you have that add method return the count of items, you can allow the method to handle thread safety internally with a much broader array of approaches.

added 13 characters in body
Source Link
JimmyJames
  • 31.1k
  • 3
  • 59
  • 111

As noted in the comments, this idea is related to the principle of Command-Query Separation (CQS). We can break this into two parts:

  1. querying (getting) an object shouldn't change its state
  2. Updating a value should not return it's state

I would say that the first is a pretty rock-solid rule. You are just asking for problems if you violate this. Maybe there's an exception but I can't think of one on a Monday.

As far as the second one goes, there are a number of reasons you might want to ignore that, or at least not return voidvoid:

Method Chaining

It's common in contemporary code to return thisthis/selfself instead of voidvoid/NoneNone. This allows you to write code like this:

object.foo(x).bar(y) instead of:

object.foo(x) object.bar(y) 

I would argue that this still follows the spirit of CQS because the return is not really querying the object. However, this approach also often involves factory methods where an object will create a new child object and return it. This would seem to violate CQS but I have no issue with it and I think it's much better than the alternatives.

Success Status

Some APIs I've worked will do things like return a booleanboolean flag indicating whether the call resulted in a change. For example, adding to a set will return true if the set was changed and false if the item was already in the set. This is useful when you want to take a one-time action upon a new value. Similarly, some map/dictionary APIs will return the prior mapped on an update. Doing this with two calls which might create flaws (e.g. TOCTOU) in some contexts such as discussed in the next section.

Atomicity

I would argue that the most clearly appropriate reason to ignore CQS (for updates) is when you can't guarantee that some other change mightwill not occur between a read and a write. For example, lets say you have a multi-threaded routine which adds items to a list. Each thread adds an item and then if the number of items and fire a notification on each hundredth item. If you need to do this in two calls, there's a potential issue: thread A adds an item making the total 500. Before thread A can get the total count, thread B adds an item making the total 501. Then both thread A and thread B get the count as 501. Therefore no event is fired on the 500th item.

To solve this under a strict CQS regime, you would need some sort of locking mechanism preventing other threads from making changes between calls. Aside from being complicated and error prone, it can create an issue with contention. If instead, you have that add method return the count of items, you can allow the method handle thread safety internally with a much broader array of approaches.

As noted in the comments, this idea is related to the principle of Command-Query Separation (CQS). We can break this into two parts:

  1. querying (getting) an object shouldn't change its state
  2. Updating a value should not return it's state

I would say that the first is a pretty rock-solid rule. You are just asking for problems if you violate this. Maybe there's an exception but I can't think of one on a Monday.

As far as the second one goes, there are a number of reasons you might want to ignore that, or at least not return void:

Method Chaining

It's common in contemporary code to return this/self instead of void/None. This allows you to write code like this:

object.foo().bar() instead of:

object.foo() object.bar() 

I would argue that this still follows the spirit of CQS because the return is not really querying the object. However, this approach also often involves factory methods where an object will create a new child object and return it. This would seem to violate CQS but I have no issue with it and I think it's much better than the alternatives.

Success Status

Some APIs I've worked will do things like return a boolean flag indicating whether the call resulted in a change. For example, adding to a set will return true if the set was changed and false if the item was already in the set. This is useful when you want to take a one-time action upon a new value. Similarly, some map/dictionary APIs will return the prior mapped on an update. Doing this with two calls which might create flaws (e.g. TOCTOU) in some contexts such as discussed in the next section.

Atomicity

I would argue that the most clearly appropriate reason to ignore CQS (for updates) is when you can't guarantee that some other change might occur between a read and a write. For example, lets say you have a multi-threaded routine which adds items to a list. Each thread adds an item and then if the number of items and fire a notification on each hundredth item. If you need to do this in two calls, there's a potential issue: thread A adds an item making the total 500. Before thread A can get the total count, thread B adds an item making the total 501. Then both thread A and thread B get the count as 501. Therefore no event is fired on the 500th item.

To solve this under a strict CQS regime, you would need some sort of locking mechanism preventing other threads from making changes between calls. Aside from being complicated and error prone, it can create an issue with contention. If instead, you have that add method return the count of items, you can allow the method handle thread safety internally with a much broader array of approaches.

As noted in the comments, this idea is related to the principle of Command-Query Separation (CQS). We can break this into two parts:

  1. querying (getting) an object shouldn't change its state
  2. Updating a value should not return it's state

I would say that the first is a pretty rock-solid rule. You are just asking for problems if you violate this. Maybe there's an exception but I can't think of one on a Monday.

As far as the second one goes, there are a number of reasons you might want to ignore that, or at least not return void:

Method Chaining

It's common in contemporary code to return this/self instead of void/None. This allows you to write code like this:

object.foo(x).bar(y) instead of:

object.foo(x) object.bar(y) 

I would argue that this still follows the spirit of CQS because the return is not really querying the object. However, this approach also often involves factory methods where an object will create a new child object and return it. This would seem to violate CQS but I have no issue with it and I think it's much better than the alternatives.

Success Status

Some APIs I've worked will do things like return a boolean flag indicating whether the call resulted in a change. For example, adding to a set will return true if the set was changed and false if the item was already in the set. This is useful when you want to take a one-time action upon a new value. Similarly, some map/dictionary APIs will return the prior mapped on an update. Doing this with two calls might create flaws (e.g. TOCTOU) in some contexts such as discussed in the next section.

Atomicity

I would argue that the most clearly appropriate reason to ignore CQS (for updates) is when you can't guarantee that some other change will not occur between a read and a write. For example, lets say you have a multi-threaded routine which adds items to a list. Each thread adds an item and then if the number of items and fire a notification on each hundredth item. If you need to do this in two calls, there's a potential issue: thread A adds an item making the total 500. Before thread A can get the total count, thread B adds an item making the total 501. Then both thread A and thread B get the count as 501. Therefore no event is fired on the 500th item.

To solve this under a strict CQS regime, you would need some sort of locking mechanism preventing other threads from making changes between calls. Aside from being complicated and error prone, it can create an issue with contention. If instead, you have that add method return the count of items, you can allow the method handle thread safety internally with a much broader array of approaches.

Source Link
JimmyJames
  • 31.1k
  • 3
  • 59
  • 111
Loading