DEV Community

Discussion on: Learn To Clone Like A Sith Lord

Collapse
 
macsikora profile image
Pragmatic Maciej

You should almost never do the deep clone. In most what you want is to change one, maybe few elements of the structure. Deep clone is the simplest but also the wrong answer for the problem.

Collapse
 
bytebodger profile image
Adam Nathaniel Davis

Interesting point. I can certainly agree that a deep clone is not needed or appropriate 100% of the time. But I can't give a blanket agreement to the idea that, if "most what you want is to change one, maybe few elements of the structure. Deep clone is the simplest but also the wrong answer for the problem."

It depends, at least partially, on individual coding style. Because I often find myself in scenarios where I need a fresh clone of an object. I've seen some devs' code where they're rarely storing data in objects. Rarely passing them around. Rarely updating their contents. For devs like that, cloning an object is probably an esoteric concept. But that wouldn't make it "wrong".

There are more decision factors that could be involved here. But for me, it usually comes down to a few basic questions:

Will we need to reference the original data structure, in its original form, at some future point in the program? Will we need another copy of that data structure which will, at some point in the future, be altered from its original form? If both of these conditions are TRUE, there's a decent chance that we might need a true, deep clone of the original data structure.

Collapse
 
macsikora profile image
Pragmatic Maciej

First of all, I am not addressing here mutation vs immutability thing (it's a huge subject on its own), I am just against unneeded deep copies.

If you have 1000 elements in array let's say and you update one element, do you clone the whole array (what mean whole is copy of every item)? If you have let's say user object and comments as nested array inside. Do you clone the user and all other comments when you want to add one comment more?

From my side both answers are no. And there are many reasons, first is memory efficiency, and many will say - haha in the FE side it's not important. It's fair argument, but if we go into such deep clone every time we can certainly got a headache on some low-end device, and maybe someday some of this code will land in node.js and there you will have for sure much bigger memory issue.

The second is equality check. As I see you are React dev then you are aware of such thing like useMemo or PureComponent. For any such deep clone any component which should not be rendered will be rendered, as its equality check will got different reference, as we did deep clone of the whole tree of things. So component which let's say was rendering data of the second comment, and it's pure one, will be re-rendered even though there is no change in this comment but some other comment was added. Finally we can end by quite a lot of rendering issues, and believe me I have been there and have seen a lot of such bugs related to unwanted reference change.

Thirdly and most importantly fact that we need deep clone means that probably our data structure is wrong, or we pass too much to consumers which should not be aware of the whole.

Let's say I do have a function addComment, should such function get user data with it's comments? No, such function should get as argument array of comments and new comment data, when we have that we are left with simple flat copy [...comments, newComment]. Such function should not be responsible for dealing with user and copying it's data. The same is true for React component, you probably not pass the whole tree to every component, and every component doesn't dig for it's part, the same should be true for changes. What is good example of such, is decomposition of updates into many reducers in Redux (like it or not, it's just a good example of updating big objects)

What I want to say is - when we decompose the data into smaller pieces there is no need for deep copying. The consumer is responsible for it's part is aware of only small piece, and copy this piece in a most flat way.

Thread Thread
 
bytebodger profile image
Adam Nathaniel Davis • Edited

If you have 1000 elements in array let's say and you update one element, do you clone the whole array (what mean whole is copy of every item)?

This is a straw-man argument that I never made. The original article never said, "You should perform a deep clone on all of your objects - regardless of the size of those objects or the scope of changes that you plan to make." Cloning objects is a tool. Sometimes a hammer is the perfect tool for the job. Sometimes it's ridiculous to use a hammer for a particular job. Showing someone how to clone !== telling someone to always clone.

Finally we can end by quite a lot of rendering issues, and believe me I have been there and have seen a lot of such bugs related to unwanted reference change.

But... this is exactly the point of cloning (where appropriate). Sometimes we need to ensure that we've broken the original references. I don't always clone an object. But when I want to avoid unwanted reference changes, I absolutely reach for cloning.

Thirdly and most importantly fact that we need deep clone means that probably our data structure is wrong, or we pass too much to consumers which should not be aware of the whole.

I get that. You don't want to pass Google to the consumer if the consumer has no need for All Of Google. No argument there. But there are absolutely times when the consumer needs All The Data from an object - but I don't want changes to the consumer's object to be reflected back in the source object.

What I want to say is - when we decompose the data into smaller pieces there is no need for deep copying. The consumer is responsible for it's part is aware of only small piece, and copy this piece in a most flat way.

This isn't "wrong" - but it makes a lot of assumptions about the code.

For one, it makes assumptions about who is the consumer, and who is the provider. For example, I often have to deal with APIs - as a consumer. I can't dictate the shape in which data is provided to me. In this scenario, I am the consumer. For example, an API response that I consume might look something like this:

{
  data: {
    user: {
      title: 'QA',
      name: 'Joe',
      id: '92354',
    },
    supervisor: {
      title: 'Tech Lead',
      name: 'Mary',
      id: '92042',      
    },
    creator: {
      title: 'Developer',
      name: 'Susan',
      id: '82991',      
    },
  }
}
Enter fullscreen mode Exit fullscreen mode

Furthermore, when I'm dealing with API results, it's often helpful (or even necessary) that I maintain a record of the original result that was returned to me. That's why I wouldn't simply pass apiResult.data to downstream consumers. Because if the downstream consumer changes that data directly on the object that was passed to them, I've now lost the record of the API response's "original state".

In that scenario, I could assign every dang value from the API into their own standalone variables, and then pass every dang standalone variable down the line to the next consumer. But that's often a verbose, and unnecessary waste of code.

So, I could pass that down to the next consumer as:

const response = {
  userTitle: apiResult.data.user.title;
  userName: apiResult.data.user.name;
  userId: apiResult.data.user.id;
  supervisorTitle: apiResult.data.user.title;
  supervisorName: apiResult.data.user.name;
  supervisorId: apiResult.data.user.id;
  creatorTitle: apiResult.data.user.title;
  creatorName: apiResult.data.user.name;
  creatorId: apiResult.data.user.id;
};
processResponse(response);
Enter fullscreen mode Exit fullscreen mode

Or... I could just do this:

processResponse(cloneObject(apiResult.data));
Enter fullscreen mode Exit fullscreen mode

And be done with it. And I'll be honest with you. I don't, in any way, feel "bad" about the second approach.

Collapse
 
bytebodger profile image
Adam Nathaniel Davis

BTW, if any of my responses sound "combative", they're absolutely not meant to be. You make many valid points. I just felt compelled to answer in the way that I did because, in my personal experience, it's almost never accurate to look at any coding technique and denounce it as "wrong". Are there times when it's a bad idea to clone an object?? Sure. Of course. But just because there are instances where cloning is a bad idea doesn't mean that all uses of cloning are a "code smell".

Some comments have been hidden by the post's author - find out more