The DNS is a better designed protocol than TCP, frankly. But I didn't explain by complaint well.
The low level programming primitives for using a TCP/IP network separate the "talk to this IP address" function from the "map this name to an IP address" function. They require the developer to implement that anew in every program or at least every higher-level library. They don't just encourage developers to think about low-level network addresses, they require it. That's a mistake that haunts both IPv4 and IPv6.
Ideally, you want the programmer to deal with the symbolic identity of the service the program wants to connect to. And they do. But the APIs make it seem like that identity is an IP address or includes an IP address. It doesn't. The IP address reflects a particular computer's current attachment to the network, not the identity of any services it happens to provide. At least, it's supposed to.
This conceptual error lies at the root of the routing system challenges, where addresses have to be messily rerouted to the current attachment when the attachment changes instead of simply taking on a new address that reflects the current attachment.