You're right, a line coverage metric is no guarantee that you have a comprehensive test. Path coverage (ensuring that every possible route through the code is exercised at least once) is far more important. However, path coverage is far more difficult to calculate than code coverage is, and code coverage is nonetheless useful (while a line that is covered isn't necessarily comprehensively tested, a line that isn't covered definitely isn't tested at all).
You should pay close attention to the points in your code where the flow of execution branches. Ternary operations (var = foo()? bar(): baz()
) should be of special interest because these are the points that can appear fully covered even when only one of the two available execution paths are followed.
It should also be mentioned that when a portion of the code is not covered by tests, it's often because it's a difficult portion of code to test for various reasons (extremely complicated logic, dependencies that can't be easily mocked, dependant on probabilistic events, intended to generate a probabilistic output, etc). This is often also the kind of code where testing is most important. If a module has 95% code coverage in its tests that's still not much use if the uncovered 5% is the part of the code that's mostly likely to harbour bugs.
If code is well designed with testing in mind (cohesive, loosely coupled, utilises dependency injection, etc) then very high code coverage of 90% or higher is definitely possible. But of course, while it's a useful metric, a high coverage figure doesn't mean your code is well tested.