> "Quite often I find difficult to decide between implementing operations as functions or as methods because I am not sure how to weight various well-known guidelines for this problem."

On it's own, it doesn't really matter all that much.  If you're more inclined towards the OOP style, you'll tend to prefer methods, otherwise, you'll think more in terms of functions and passing data around. Both are fine. Functions vs methods is not the real issue. The core issue is how you represent the problem domain (and its subproblems) in code - how conceptualize the problem and express these concepts in code, how you decompose things, and how you control interrelationships between things.

> " * The favored answer in this (very related) question suggests that methods are better suited when the action is performed by the object and not on the object, which is the current case."

This is a matter of perspective ("by" vs "on") - i.e. it depends on how you think about the problem. After all, as SE user *besc* pointed out in a comment to your question, "`person.calculate_diabetes_risk()` is essentially the same as `calculate_diabetes_risk(person)`". The by/on dichotomy doesn't really help.

> " * Semantically, I often heard that functions are more adequate for modelling *procedures* or *algorithms* while methods for *behaviors*."

Same problem. Without defining what the essential difference is between *procedures* and *behaviors*, this doesn't really help either.

Plain data structures, functions, and objects can work together, though, and they can complement each other nicely. They all afford different kinds of abstractions that let your code be more expressive. Data structures let you model (represent) concepts as a small collection of properties. Functions let you abstract away a procedure behind a (hopefully well chosen) name, and control the coupling with *other code* via a well defined set of inputs and outputs. In terms of designing for change, functions are good when data structures they use are relatively stable - you can easily add functions that work with same data structures, but then changing those data structures could be a chore. 

Objects bring a couple of new things to the table, and also invert this dynamic. They are like tiny computers that bundle a number of related methods, maintain their internal state, and enforce rules governing state changes (or, if immutable, enforce those rules across copies). They can be substituted for each other if they implement the same interface. Objects are good if the interface in question is more stable compared to internal representations of implementations - it is easy to add a new representation, but hard to change the interface. 

Finally, objects have constructors, and this is neat because they let you preconfigure them, and pass these preconfigured instances around. You can sort of do the same thing with partially applied functions, lambdas, or closures - in this light, you can also see objects as a more versatile version of that.

Conventional OOP wisdom would tell you to look for nouns in your problem domain, as these are likely to be good candidates for objects. E.g. maybe Patient should be an object. But this is not the only way to do OO. Sometimes, these concepts that we find at the beginning are better represented as data structures that are passed around. Instead, objects come up later on, as your understanding of the domain improves. Maybe things that manipulate these data structures are better represented as objects. Maybe a some computation or behavior can be more elegantly represented if it's associated with a computation-specific state. 

> "Was my initial decision flawed"

Yes. But here's the kicker: your initial decision is *always going to be flawed* to some extent. That's essentially one of the defining features of our discipline. 

> "and should have gone with the method since the beginning? If yes, how can I better recognize when I should do this? Or, is the method always the right choice for this scenario?"

No. You don't magically find a design at the start that will then somehow turn out to work well for every change and new development that comes along. We have a name for that idea - [waterfall][1]. See also [Big Design up Front][2]. Unless the problem you're working on is *very* well understood<sup>1</sup>, the waterfall approach doesn't work, and even then there will be aspects surrounding the problem that aren't waterfall-y. 


------------------

<sup>1</sup> "well understood" - What I mean by that is: the rules governing the problem are understood. There's a body of documented knowledge and experience surrounding the problem. E.g., maybe there's math describing (aspects of) the problem, there are standards, published data about things like tolerances, experimental support, etc. 

--------------------------

You came up with a design that seemed reasonably good at the time, based on what you knew about the problem at that time. Sure, if you happen to work on a problem that has aspects that are familiar to you, you may come up with a better educated guess about what the design should be. And with experience, you'll be able to recognize certain features of the problem more easily. And maybe for a certain class of problems you'll maybe start with a cookie-cutter solution (e.g., some starter skeleton code with an accompanying list of "best-practices"). 

What you did initially is fine. What you do as things progress is more important. Because over time, things are likely to "conspire" to make your initial assumptions, and your initial design, less and less suitable. That's what agile is all about - learning on the go, and responding to this new knowledge. The problem is that people forget the "responding" part. 

> "but I have heard some concerns that Patient will become a god object. Is this something I have to be careful of?" 

It will, but only if - (1) that part of the codebase gets worked on a lot, and (2) if the developers working on it don't stop and reevaluate the design when they sense there's friction. You can't be agile by being rigid.

This phenomenon is called code rot. Now, people usually say that an important factor that drives code rot is that over time, due to things like deadlines, etc., we tend to sacrifice design and turn more towards workarounds and hacks, but I don't think that's the case. The problem is that we never reevaluate the initial designs, and then work within the constraints imposed by it.

You see, large, bloated spaghetti functions, classes, modules, etc., get built by doing the same thing over and over. By following previously established principles over and over, well past their expiration date. You'll see this kind of thing in codebases that have existed for some (not necessarily very long) time: a 10,000 line file will have a bunch of functions (or methods), many of which follow the same pattern (e.g. they take arguments in a certain order, where arguments play a certain role, and many they do, internally, what are conceptually conceptually similar things, but there's no real way to [DRY][3] them out). And it's because people kept doing things the way they were done initially, without ever stopping and coming up with a different abstraction that will let them re-express their code in a simpler way. Heck, there may be even practices put in place (like code reviews) that actively discourage straying from this established path.

This is often done with good intentions - in an attempt to be professional, to maintain a certain level of quality, to do things "by the book". The problem is, the metaphorical book was written at the very start when a lot was unknown, so it has to be updated from time to time - and, initially, quite a bit more often than you'd think. This is why agile has iterations, sprints, retrospectives. Do a small number of things, get feedback as soon as possible, learn from it, update your understanding of the problem and reevaluate the design, then do it again, and again.

> "how can I prevent it?"

Literally by going: "Oh. This class is getting a little too big and complicated for what I'm trying to do [or, if in a team: for what we're trying to do]. Let me see if I can somehow split or rearrange the code make things simpler, based on what I know about the problem domain *now*."

An important point is that this is not just about splitting a big class into a couple of smaller ones. Just splitting is not enough. You have to do it in a way that buys you more high level expressiveness than what you had before (good names for classes and methods, and free functions, are a big part of that). Something that lets your code (that now calls these subproblem-specific functions or classes you extracted) read a bit more declaratively. And while you're at it, look for opportunities to redesign the code to better support your needs as you currently understand them (in terms of what's actually happening in the domain, what the code needs to do, and in what ways it changes most often). I.e., reconceptualize how you express things in code to better reflect how you think about the problem now.

There will be parts of the codebase that don't get changed a lot. They may have a horrible design, but they work, and since they don't experience change often, their design is really not that important. It's where you keep changing things where design matters. The problem is, people rarely think about the design while they are changing code; it only becomes an obvious problem when it's too late - when you end up with that one huge file that needs to be updated fairly often, but that everybody is afraid to touch because its hard to understand and changing it is likely to break things in bizarre ways.

  [1]: https://en.wikipedia.org/wiki/Waterfall_model
  [2]: https://en.wikipedia.org/wiki/Big_Design_Up_Front
  [3]: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself