So, roughly speaking, many of these problems arise because Linux is a weak end system in an RFC1122 sense (https://www.rfc-editor.org/rfc/rfc1122.html). This is by design, not a bug, etc -- the "meaning" is roughly that to Linux, IP addresses belong to the kernel, not the interfaces themselves. This is the right way to think about the problem.
There are a number of features available to Make It Work The Way You Want but they're often nonobvious. As a sibling comment notes, you can set the arp_announce and arp_ignore (and arp_filter) sysctls to instruct the kernel to answer or not answer for arp requests based on interface specifics (the other arp_ sysctls are related to gratuitous arp sending and receipt).
As in this post, you can use the rp_filter sysctl to change the reverse path filtering (as in RFC3704).
Additionally, you can use iproute2 rules and route tables to enforce outbound paths if you want a node to have multiple interfaces on the same subnet without arp confusion.